Skip to content

UI/UX interaction conventions for sdk-parity features #87

Description

@goanpeca

UI/UX interaction conventions for sdk-parity features

This issue is the single source of truth for how the ~22 sdk-parity feature issues (#56#78) surface their UI in b2-vscode. Every new feature MUST conform to the rulings below. PRs that diverge should be sent back to match the relevant family pattern.

Native-first principle

The extension today uses only native VS Code primitives — zero webviews, zero custom editors, zero virtual-document providers. Files are viewed by downloading to a temp file and opening via vscode.open. Menu gating is done with setContext keys + when clauses. The default bar is: stay native. A webview or custom editor is a new, heavier dependency and is presumed non-compliant unless it passes the three-part gate below.

Current vocabulary (precedent set)

  • TreeView: createTreeView for b2Buckets (src/extension.ts), single-select today.
  • Transient input: showInputBox, showQuickPick, showInformationMessage/showWarningMessage/showErrorMessage (src/commands/index.ts).
  • Progress + cancellation: withProgress(Notification) / withCancellableTransferProgress (src/services/transferProgress.ts).
  • Destructive confirm: { modal: true } — irreversible actions only.
  • Clipboard / open: env.clipboard.writeText, vscode.open (src/commands/index.ts).
  • Gating: setContext (src/services/authService.ts:101, key b2.authenticated) + when clauses; existing config-mutation precedent src/commands/publicBucketVisibility.ts.
  • Status bar: src/ui/statusBar.ts. Menu groups: navigation, 1_copy, 2_bucket, 3_destructive, inline; palette-hidden item-commands via commandPalette "when": false.

Webview / custom-editor gate

A webview/custom editor is allowed ONLY if it answers YES to all three: (1) content is genuinely visual or interactive (charts, image preview, drag-reorder canvas) — not a form, property list, or JSON; (2) it cannot be expressed natively without material loss of function; (3) it does not duplicate a Family A (config-edit) or Family B (detail-view) need. Forms, config editing, key creation, property/detail views, and list browsing are never valid webview justifications. No current issue passes the gate — the warranted-webview set is empty.

Family rulings

Family A — bucket config-edit (#65, #66, #67, #68, #70, #78). Edit-as-JSON-document, native, no webview. Ship the identical trio on the shared b2-config: scheme:

  • B2: Show <Config>… — read-only virtual JSON doc via one shared TextDocumentContentProvider, URI b2-config:/<bucketName>/<kind>.json, languageId: jsonc. Diffable/copyable/scriptable read path.
  • B2: Edit <Config>… — editable untitled jsonc doc seeded with the current config + a comment banner naming the bucket, config kind, and apply command; sets a b2.editingConfig:<kind> context key.
  • B2: Apply <Config> — editor-title action gated by when on that key: parse → validate against the schema → abort with showErrorMessage and no partial write on failure → apply inside withProgress(Notification) → refresh → success message. Destructive deltas (clear-all / replace ruleset) require { modal: true }.

Mandatory for array/nested/free-map configs. The ≤~3-enumerable-field QuickPick downgrade applies ONLY to #69 default-SSE (which follows the changeBucketVisibility QuickPick+modal variant, not the JSON trio).

Family B — rich read-only detail (#61, #77). ONE shared b2-detail: TextDocumentContentProvider, rendered as Markdown property tables, opened ViewColumn.Beside, read-only. Per-field copy via env.clipboard.writeText. No webview, no showInformationMessage wall-of-text. #61's optional content-type/fileInfo edit is a separate Family A (edit-as-JSON) flow.

list-browse (#59, #63, #64, #71). Extend the TreeView; never a QuickPick-as-list or webview. Child-nodes when data is intrinsically part of a node (#59 versions); a NEW sibling view inside the existing b2Explorer container for top-level collections (#64 keys, #71 unfinished uploads). #63 search filters the existing b2Buckets tree (reusing LoadMoreTreeItem/ListingLimitTreeItem), never a QuickPick.

file-action (#58, #60). Native context-menu commands + withProgress + refresh(); reuse renameFile copy-then-delete + B2PartialFailureError. { modal: true } only for irreversible deletes (not reversible hide). #60 unhide is paired with #59 (needs a hide-marker node) or setContext-gated.

file-transfer / file-I/O (#56, #57, #72). Native showOpenDialog/showSaveDialog (no silent temp writes) + withCancellableTransferProgress honoring the AbortSignal; TreeDragAndDropController on b2Buckets for OS-file drops; sync dry-run plan in a modal summary + OutputChannel/untitled doc.

bulk-multiselect (#73). canSelectMany:true set once on b2Buckets; commands accept (item, items[]); single modal count-summarized confirmation; one cancelable withProgress; per-item failures summarized without aborting. #57/#58/#71 compose against this rather than re-adding it.

clipboard-export / secret-display (#62, #75; secret pattern also #64, #69). env.clipboard.writeText + showInformationMessage in 1_copy for non-secrets; modal+Copy or untitled/discardable editor for secrets — never persisted, never logged, never in settings/globalState.

capability-gating (#76). setContext keys in src/constants.ts, set/cleared in AuthService.setAuthState, consumed by when clauses. Never imperative greying or showErrorMessage('not allowed').

Per-issue table

Issue Family Primary primitive Native? Note
#56 file-transfer showOpenDialog + withProgress + TreeDragAndDropController yes Upload direction; modal overwrite confirm.
#57 file-transfer showSaveDialog/showOpenDialog + withCancellableTransferProgress yes First real save/open dialog; checksum-abort.
#58 file-action multi-step showQuickPick dest + withProgress copy/delete yes Reuse renameFile + B2PartialFailureError.
#59 list-browse TreeView child-nodes under file yes Versions belong to a file; not a QuickPick.
#60 file-action view/item/context commands + withProgress + refresh yes Hide = reversible (no modal); unhide paired with #59.
#61 detail-view b2-detail: read-only virtual doc (Markdown) yes Same provider/format as #77; edit path = Family A.
#62 clipboard-export showQuickPick TTL + clipboard.writeText yes Token is a one-time secret (§6).
#63 search/list-browse filter the b2Buckets TreeView yes Drop the QuickPick branch; reuse LoadMore.
#64 multistep-create + list-browse + secret new b2Keys view + multi-step QuickPick + modal+Copy yes §5 creation anchor; sibling view in b2Explorer.
#65 config-edit (A) edit-as-JSON trio (b2-config:) yes Array config → JSON mandatory.
#66 config-edit (A) edit-as-JSON trio (b2-config:) yes Validate ops vs CorsOperation.
#67 config-edit (A) edit-as-JSON trio (b2-config:) yes Nested rules → JSON mandatory; modal on clear.
#68 config-edit (A) edit-as-JSON trio (b2-config:) yes Drop ad-hoc per-field chain; HMAC secret in JSON not logged.
#69 config-edit + file-transfer showQuickPick (SSE mode) + password InputBox + withProgress yes Bounded enumerable → changeBucketVisibility variant, NOT JSON trio.
#70 config-edit + file-action JSON trio (default retention) + multi-step QuickPick (per-file) yes Default retention = Family A; governance bypass = setContext-gated modal.
#71 list-browse new b2UnfinishedUploads view (canSelectMany) yes Sibling view; modal cancel/cancel-all.
#72 file-transfer multi-step QuickPick + showOpenDialog + withCancellableTransferProgress yes Dry-run modal + OutputChannel, no webview.
#73 bulk-multiselect canSelectMany + (item, items[]) commands yes Shared prerequisite for bulk download/move.
#75 secret/clipboard-export modal+Copy / untitled ini doc + clipboard.writeText yes Pairs with #77; §6 secret handling.
#76 capability-gating setContext keys + when clauses yes Gating mechanism every conditional feature reuses.
#77 detail-view b2-detail: read-only virtual doc (Markdown) yes Same provider/format as #61; pure read-only.
#78 config-edit (A) edit-as-JSON trio (b2-config:) yes Free-form map; split single command into Show/Edit/Apply.

All 22 issues resolve to native primitives. No webview or custom editor is warranted.


Update — fitness-neutral re-pass

A second audit re-evaluated every feature with the native-first bias removed: native vs. custom-editor vs. webview scored on fitness, each recommendation run through a symmetric adversarial challenge (webview picks had to survive "native suffices"; native picks had to survive "this is forced/degraded"). Honest result: of 22 features, 17 confirm native as the genuine best fit, 0 in-scope issues warrant a webview/custom editor, with 5 "right call, wrong native primitive" corrections and 1 cross-cutting webview capability (filed as #101).

Corrections to the rulings above — all stay native

# Was Now Why
#67 edit raw JSON multi-step QuickPick + live bucket/key pickers JSON enums can't bind live account resources (destinationBucketId, sourceApplicationKeyId)
#72 modal + OutputChannel checkbox TreeView plan (select before apply) a destructive delete-by-prefix plan must be actionable, not a read-only dump
#77 read-only Markdown QuickPick with copy-per-item Markdown fails the issue's own "copy each endpoint" criterion
#70 JSON as default multi-step QuickPick; JSON = opt-in escape hatch JSON overkill for a 2-field retention policy
#61 read-only Markdown hybrid read + light edit the read-only doc had no edit path for content-type/fileInfo
#65 Show/Edit/Apply trio text-backed editor (save persists, reload re-reads) trio slightly overbuilt; JSON+schema family still correct

Each corrected issue carries a detailed comment.

The one genuine webview case → #101

A unified multi-transfer manager dashboard (uploads + downloads + sync + batch, with per-item pause/resume/throughput). withProgress is hard-limited to one task per call — a real native dead-end. It is cross-cutting (no single issue covers it), so it is filed separately as #101, and must degrade on web/remote hosts (#5).

Caveat: the re-pass agents read the issue requirements via gh but ran in a different working directory and could not grep the extension source, so the 5 re-homings should be sanity-checked against the real code (only the de-weighted "consistency" criterion is affected).

When native is the wrong choice (and when it isn't)

When native is the wrong choice (and when it isn't)

Native VS Code primitives (TreeView, QuickPick, InputBox, withProgress, virtual documents, JSON+schema, context-menu commands, setContext/when) are the right default. They are free, themed, accessible, keyboard-native, and fully portable across web/remote/virtual hosts (issue #5). A webview or custom editor should be reached for only when a specific, load-bearing signal fires — and never to "look richer."

The five signals that justify a non-native surface

A richer surface (webview / text-backed custom editor) is warranted only when at least one of these is genuinely present at the issue's defined scope:

  1. Information density / simultaneity — the user must reason over many panels or fields at once, and a one-at-a-time QuickPick or a flat document materially degrades the task.
  2. Many interdependent / conditional fields (the QuickPick cliff) — roughly >5 interdependent fields, or conditional layout where one choice re-shapes others. (Independent fields, even ~5 of them with simple Back-navigation, stay inside the QuickPick envelope.)
  3. Inherent visual / graphical / spatial content — charts, topology, drag-to-reorder canvases, timelines. A scalar (a count, a size, a part number) is not graphical.
  4. Live multi-item dashboard state — many concurrent items updating in real time with per-item controls (pause/resume/retry/throughput). A static inventory (a list you fetch and act on) is not this.
  5. A real, persistent, savable/diffable document type — and even then this points to a text-backed custom editor, not a webview.

If none fire, native is not merely adequate — it is the highest-fitness answer, and a webview is pure cost (CSP hardening, bundle, message-passing, lifecycle, re-implemented theming/a11y) plus a host-portability regression against issue #5.

The trap to avoid first: the weak native primitive

The most common real defect is not "should have been a webview" — it is right family, wrong native member. Before overturning native to a richer surface, check that you picked the fit native primitive:

Two security-specific rules

The host-portability tiebreaker (#5)

When a webview and native tie on raw capability, native wins because issue #5 targets web/remote/virtual hosts where a webview is constrained and heavier. Storage and credential-minting flows in particular must keep working in Codespaces/vscode.dev, so the tie never breaks toward the webview.

The honest exception

The one place a webview is genuinely correct in this codebase is not any single feature — it is the cross-cutting transfer manager (concurrent up/downloads + sync + batch with per-item pause/resume/cancel and live throughput). withProgress is hard-limited to one task per call and cannot host a live multi-item dashboard. That is signal #4 firing for real, across four otherwise-native features — and it deserves its own scoped issue rather than bloating any of them.

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationpriority: mediumMedium prioritysdk-paritySDK capability not yet exposed in the extension UI

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions