Skip to content

feat(deck): build a native kanban workspace - #221

Merged
veryCrunchy merged 36 commits into
mainfrom
feat/52-native-deck-workspace
Jul 27, 2026
Merged

feat(deck): build a native kanban workspace#221
veryCrunchy merged 36 commits into
mainfrom
feat/52-native-deck-workspace

Conversation

@veryCrunchy

@veryCrunchy veryCrunchy commented Jul 26, 2026

Copy link
Copy Markdown
Member

What changed

  • negotiates supported Deck API versions without masking authentication or permission failures
  • loads typed boards, lists, cards, labels, assignees, dates, comments, attachments, and relationships
  • routes Deck into a native board picker, kanban workspace, card details, and guarded dialogs
  • supports board, list, and card creation and editing through typed server-derived identities
  • supports labels, assignees, completion state, local quick due dates, comments, attachments, and card relationships
  • streams bounded attachment downloads to native apps and persists one-shot attachment uploads safely across Android lifecycle changes
  • moves and reorders cards using Deck's authoritative zero-based position contract
  • adds reusable drag feedback and applies the same native interaction to compatible non-Deck board schemas
  • preserves discovered lane order and disables writes while identity or mutation outcomes are stale or ambiguous
  • reconciles ambiguous board deletion, restoration, relationships, uploads, and card mutations against authoritative state
  • preserves complete editable card state, Markdown, unparseable due values, board metadata, and cached boards during refresh failures
  • keeps card actions behind overflow and long press while preserving primary card opening
  • replaces duplicate board headers with one contextual header
  • preserves board, selected-card, and draft state across Android and desktop lifecycle changes
  • encrypts bounded desktop drafts with an app-private key stored through the desktop Secret Service
  • adds deterministic mobile and desktop Compose captures for visual review

Why

Deck previously appeared as generic API records, so its data was visible but the workflow was not useful. This branch establishes a native kanban experience backed by explicit Deck semantics and guarded write contracts. The same interaction model also works for dynamically discovered board-shaped apps without depending on a Deck app identifier.

Verified

  • focused Deck and board desktop tests pass on JDK 21
  • Android debug unit tests pass
  • Android debug and instrumentation-test Kotlin compilation pass
  • all 15 deterministic Compose capture scenarios regenerate successfully
  • website tests, client build, SSR build, and prerender pass
  • repository hygiene, changelog, release-tooling, and punctuation checks pass
  • synthetic desktop and compact mobile captures show one header, native lanes, cards, and horizontal board navigation
  • user device testing confirmed native card dragging

Remaining review gates

  • wait for exact-head CI and automated review after the main integration
  • verify any new correctness findings against the implementation before changing code
  • keep destructive and ambiguous write paths guarded until disposable-server mutation coverage is available
  • complete another visible Android rotation and large-screen interaction pass before closing the wider Deck issue

Advances #52

@veryCrunchy
veryCrunchy marked this pull request as ready for review July 26, 2026 15:53

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fabe0863f9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/DeckNativeActions.kt Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ed8193a313

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NativeDeckScreen.kt Outdated
Comment thread ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NativeDeckScreen.kt Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7d5dbe6cfe

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NativeDeckScreen.kt Outdated
Comment thread ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NativeDeckScreen.kt Outdated
Comment thread ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NativeDeckScreen.kt Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

https://github.com/Obiente/nc-native/blob/d52ec5787131955b3e157b4cbccaab64472060f6/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/nativeui/runtime/GenericNativeRenderer.kt#L1787-L1788
P2 Badge Keep dynamic move reconciliation across refreshes

After a discovered-board drag succeeds, this callback increments the host's loadAttempt; the forced reload sets viewState to Loading, removing GenericRecordBoard from composition and discarding its remembered pendingMove. When the refreshed board returns, the verifier therefore cannot confirm that the record reached the requested lane or report that a 2xx move was ignored. Hoist the pending receipt above the loading/ready branch or keep the board composed while refreshing.

AGENTS.md reference: AGENTS.md:L192-L194

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/DeckMutationSafety.kt Outdated
@veryCrunchy veryCrunchy added priority:P0 Required for a trustworthy mobile replacement or prevents data loss area:dynamic-apps Adaptive contracts, semantic UI, and app integrations labels Jul 26, 2026
@veryCrunchy veryCrunchy added this to the 0.5 Adaptive apps milestone Jul 26, 2026
@veryCrunchy veryCrunchy added type:feature New product capability platform:android Android-specific implementation or validation platform:desktop Desktop-specific implementation or validation labels Jul 26, 2026
@veryCrunchy veryCrunchy moved this to In Progress in Nextcloud Native Roadmap Jul 26, 2026
@veryCrunchy
veryCrunchy merged commit f366ab9 into main Jul 27, 2026
2 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Nextcloud Native Roadmap Jul 27, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 92b814dd11

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +800 to +804
}.getOrElse { error ->
DurableUploadEnqueueResult.Rejected(
error.message?.take(MAX_DURABLE_UPLOAD_MESSAGE_CHARACTERS)
?: "The attachment upload failed.",
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve unknown upload outcomes on desktop

On Linux desktop, which does not override this default, a transport exception or HTTP 5xx after Deck accepts the multipart body is collapsed into Rejected, so the screen releases the selected file and reports a definite failure; retrying can then create a duplicate attachment. Fresh evidence after the earlier upload-lifecycle finding is that Android now has durable outcome tracking while DesktopNextcloudServices still uses this synchronous fallback; classify ambiguous results as outcome-unknown and retain recovery state until the card is reconciled.

AGENTS.md reference: AGENTS.md:L187-L194

Useful? React with 👍 / 👎.

Comment on lines +1009 to +1013
onCreateStack = currentBoard?.takeIf { it.permissions.canManage }?.let { board ->
{ interaction = DeckUiInteraction.StackEditor(board, null) }
},
onCreateCard = currentBoard?.takeIf { it.permissions.canEdit }?.let {
{ stack -> interaction = DeckUiInteraction.CardEditor(stack, null) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Hide active-board mutations after archival

When another client archives the currently open board and the authoritative refresh updates board.archived, these creation callbacks remain exposed because they check only permissions; stack/card actions and drag use the same incomplete condition. The resulting dialogs or drag interaction accept user input only for requireActiveManage/requireEdit to reject the request as read-only, so include !board.archived when deriving all active-board mutation affordances. Fresh evidence after the archived-comment finding is that comment controls now honor archival, but these non-comment controls still do not.

AGENTS.md reference: AGENTS.md:L168-L169

Useful? React with 👍 / 👎.

Comment on lines +612 to +614
val durableUploads = runCatching {
services.durableMultipartUploadStatuses(session, attachmentUploadScope(card))
}.getOrDefault(emptyList())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Refresh background upload status while the dialog is open

On Android, this reads durable upload status only when attachments are loaded, and the post-enqueue read normally occurs before WorkManager finishes; there is no polling or worker-driven refresh afterward. A dialog left open therefore continues to show "Waiting for background upload" after completion or failure and does not reveal the new attachment until it is closed and reopened; observe or poll active jobs and refresh the authoritative attachment list when they become terminal.

AGENTS.md reference: AGENTS.md:L217-L218

Useful? React with 👍 / 👎.

val discoveredLanes = remember(resource, records, declaredLanes) {
declaredLanes ?: nativeBoardLanes(resource, records)
}
val initialLaneOrder = remember(resource.id) { discoveredLanes.map(NativeBoardLane::key) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scope retained lane order to the active board context

When a discovered app switches between views or parent boards that reuse the same resource.id, this remember retains the first board's lane order and applies it to the new board. DynamicDiscoveredAppScreen reuses the same renderer call as selectedView and navigation context change, so two boards backed by a common resource such as cards can display the second board's lanes in the first board's order; key this state by the app, view, and active container/parent identity rather than the resource type alone.

AGENTS.md reference: AGENTS.md:L154-L161

Useful? React with 👍 / 👎.

Comment on lines +86 to +87
value.descriptionMarkdown.length > DECK_UI_CARD_DESCRIPTION_LIMIT ->
"The description is too long."

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate descriptions against the encoded request limit

This validation counts UTF-16 characters, while deckJsonRequest later rejects the UTF-8 JSON body above 96 KiB. For example, a description containing 40,000 ordinary CJK characters is below the advertised 65,536-character limit but encodes to roughly 120 KiB before JSON overhead, so the enabled Save/Create action always fails during request construction; validate the encoded request size in the form or align the character and transport limits.

AGENTS.md reference: AGENTS.md:L173-L183

Useful? React with 👍 / 👎.

@veryCrunchy veryCrunchy mentioned this pull request Jul 27, 2026
9 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:dynamic-apps Adaptive contracts, semantic UI, and app integrations platform:android Android-specific implementation or validation platform:desktop Desktop-specific implementation or validation priority:P0 Required for a trustworthy mobile replacement or prevents data loss type:feature New product capability

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant