162 lines
10 KiB
Markdown
162 lines
10 KiB
Markdown
# ADR-2026-08-06-plugin-code-host-dashboard-parity: Code-Host Plugins Reuse Native Dashboard Primitives
|
|
|
|
**Status:** accepted
|
|
**Date:** 2026-08-06
|
|
**Area:** frontend, protocol
|
|
|
|
## Context
|
|
|
|
GitHub and GitLab dashboards share one interaction model, but their list and toolbar
|
|
components remained provider-coupled. The first Bitbucket plugin therefore reproduced
|
|
their appearance with plugin CSS while inventing a second dashboard review action and
|
|
an intermediate launch dialog. Visual similarity did not preserve the native task and
|
|
review workflow.
|
|
|
|
## Decision
|
|
|
|
Kandev owns provider-neutral code-host dashboard primitives under
|
|
`apps/web/components/integrations/` and exposes them through `host.ui`. GitHub, GitLab,
|
|
and compatible plugins render their result list, row, toolbar, scope controls, task
|
|
preset menu, linked-task indicator, and semantic change-request icons through those
|
|
same host components. Plugins select icon semantics such as open, merged, or closed;
|
|
they do not copy SVG paths from first-party providers.
|
|
|
|
Task presets use the same provider-neutral semantic icon keys as first-party presets.
|
|
The host resolves those keys to its Tabler components, so **Review**, **Address
|
|
feedback**, and **Fix CI** render the exact eye, message, and tool glyphs everywhere.
|
|
Plugins do not pass custom icon components or rely on a generic fallback when a native
|
|
semantic equivalent exists. Provider brand glyphs are different: the plugin owns that
|
|
component and Kandev reuses it across native surfaces.
|
|
|
|
Kandev also owns common task change-request linking anatomy. Code-host plugins invoke
|
|
the host task-link dialog contract with provider copy and a submit callback; they do not
|
|
rebuild the input, validation, loading state, footer, toast, close behavior, or responsive
|
|
surface inside an arbitrary plugin modal. A Link-submenu child names the target only
|
|
(for example, **Bitbucket Pull Request**) because the parent already supplies the verb.
|
|
The singleton plugin modal host mounts inside `AppShell`'s theme, tooltip, and toast
|
|
provider tree so host-owned workflows retain the same runtime contexts as first-party
|
|
dialogs.
|
|
|
|
Runtime React components remain in the Kandev host repository and are delivered by
|
|
the versioned `host.ui` contract. They do not move to a separate shared frontend
|
|
repository: host-owned React, Radix contexts, theme behavior, and release compatibility
|
|
must remain atomic. A future published SDK package may provide TypeScript contracts
|
|
and pure build-time helpers once multiple plugin repositories need them, but it must
|
|
not duplicate runtime UI implementations.
|
|
|
|
A code-host dashboard row is an inert result container. Its title opens the provider,
|
|
its metadata describes the change request, and its sole creation control is the compact
|
|
**Task** preset dropdown. Selecting a preset opens the host's real
|
|
`TaskCreateDialog` directly; provider linking runs after successful task creation.
|
|
Dashboard rows do not add a **Review** action or a provider-owned intermediate launch
|
|
modal. Review begins from linked task context through the existing review-provider
|
|
registry on desktop and mobile.
|
|
|
|
When the selected repository already exists in the workspace, the dialog keeps its
|
|
normal REST create transport. For a first-use plugin repository, the plugin may inject
|
|
the dialog's provider-neutral `createTask` transport and forward only native task
|
|
fields through an authenticated plugin action. The plugin server resolves the pull
|
|
request and trusted repository descriptor from its connection before calling Host
|
|
`Tasks.Create`; browser input never grants repository-provider authority. A per-dialog
|
|
launch identifier makes transport retries idempotent without limiting one pull request
|
|
to one task.
|
|
|
|
Kandev also owns task-scoped change-request status presentation. A review-provider
|
|
summary may include normalized status/check data; the host review registry preserves it,
|
|
refreshes it through the existing provider lifecycle, and automatically mounts the same
|
|
shared control in both first-party locations: the task topbar and the status row above
|
|
the chat composer (including passthrough sessions). The host owns the exact trigger,
|
|
delayed desktop hover popover, native mobile drawer, accessibility, touch geometry, and
|
|
responsive behavior. Providers do not register a visual status slot. They supply data,
|
|
refresh it around every 90 seconds, and retain provider API ownership.
|
|
|
|
The shared status body is the provider-neutral presentation extracted from GitHub's CI
|
|
popover. Its common header, grouped checks, review/comment summaries, links, loading,
|
|
empty, and error states render identically for built-ins and plugins. Provider-only
|
|
controls such as automation or merge actions are optional callbacks/capabilities inside
|
|
that anatomy, not alternate markup. Opening Review routes through the registered review
|
|
provider on desktop or the current mobile session.
|
|
|
|
Kandev likewise owns the common change-request detail presentation exposed through
|
|
`host.ui.ChangeRequestDetail`. GitHub and compatible review providers map their native
|
|
payloads into one normalized detail model and pass supported action callbacks. The host
|
|
owns header, branch/state metadata, description, reviews, checks, threaded comments,
|
|
action placement, loading/error states, scroll ownership, and responsive behavior.
|
|
Providers retain queries, authorization, mutation semantics, capability detection, and
|
|
normalization. A plugin may still own a review-panel adapter, but not a parallel visual
|
|
design for those common surfaces.
|
|
|
|
Every successful PR-from-task creation and watch-created task records the same durable
|
|
task-to-change-request association exposed by manual linking. Watch-owned links remain
|
|
authoritative in watch state and are unioned with manual link state when serving task
|
|
review/status and workspace association queries; implementations do not copy them into
|
|
a second store merely for presentation. Durable association, suppression, reservation,
|
|
and watch keys use provider ID, provider connection scope, immutable repository ID, and
|
|
change-request number. Repository paths, display keys, and clone URLs are mutable lookup
|
|
attributes and never durable identity. A changed connection epoch pauses an existing
|
|
watch before provider access; resuming explicitly rebinds it and discards its old cursor.
|
|
|
|
Task-scoped refresh also performs bounded branch-based discovery, equivalent to
|
|
GitHub's external-PR detection. It derives repository and checkout branch only from the
|
|
host-verified task, exact-matches an open provider change request's source branch, and
|
|
stores the result idempotently. Browser-supplied repository or branch values never
|
|
authorize automatic linking, and discovery failure does not hide an existing link.
|
|
|
|
The native task dialog's branch picker follows the same provider-neutral trust boundary.
|
|
For a persisted plugin-owned repository, the host resolves the active manifest owner and
|
|
invokes its declared workspace-scoped `repositories.branches` action with a descriptor
|
|
derived only from the stored repository row. The action returns normalized branch names;
|
|
the browser cannot substitute provider identity, host, namespace, clone URL, or repository
|
|
ID. GitHub remains on its first-party service path, while future code-host plugins reuse
|
|
this action instead of adding another host-native provider branch.
|
|
|
|
Provider adapters continue to own API queries, normalization, authorization, task-link
|
|
storage, provider data, and capability handling. Shared host components own presentation
|
|
and interaction geometry; they accept normalized data and callbacks and contain no
|
|
GitHub, GitLab, or Bitbucket API logic.
|
|
|
|
Repository discovery is a provider-neutral, server-searchable paged contract. The host
|
|
passes optional `query`, opaque `cursor`, and requested `limit` fields; providers return
|
|
either the legacy repository array or `{ repositories, nextCursor }`. The host follows
|
|
pages, rejects repeated cursors, and always binds returned provider identities to the
|
|
manifest owner. Registered plugin-owned provider icons are used in native repository
|
|
controls rather than a generic branch glyph. These rules keep large installations usable without adding
|
|
provider-specific search or pagination branches to Kandev. Provider cursors are bound to
|
|
the original query, state, provider connection scope, and immutable repository identity;
|
|
using one after any of those inputs changes fails before a provider request.
|
|
|
|
Task indicator and CI refreshes are summary operations. They fetch only normalized
|
|
change-request metadata, reviews, checks, and an inexpensive accurate unresolved-comment
|
|
count when the provider exposes one. Files, diffs, commits, and comment bodies load lazily
|
|
when Review opens. Both summary and detail responses remain bounded, with explicit
|
|
truncation instead of exceeding the authenticated plugin action response limit.
|
|
|
|
Pipeline/build status is read from the pull request's source (head) commit. The
|
|
destination commit describes the merge target and cannot represent the CI result for
|
|
the proposed change.
|
|
|
|
## Consequences
|
|
|
|
Code-host plugins require a host release containing these additive `host.ui` exports.
|
|
Dashboard fixes now apply uniformly to first-party and plugin providers, including
|
|
themes, touch targets, dropdown behavior, loading/error states, and row density.
|
|
Provider adapters remain independently releasable, while common review/status visuals
|
|
enter the versioned host contract. New semantic icon or presentation needs require an
|
|
additive host contract release rather than a plugin-local visual approximation. Plugin
|
|
repositories avoid a second runtime UI dependency and its version-skew/release-
|
|
coordination cost. Task-link, status, and detail fixes land once in the host and remain
|
|
consistent for first-party and plugin providers, without moving provider polling,
|
|
credentials, or API payloads into Kandev.
|
|
|
|
## Alternatives Considered
|
|
|
|
- Keep copying GitHub/GitLab markup and CSS into each plugin. Rejected because the
|
|
first live implementation already drifted in behavior despite similar styling.
|
|
- Export GitHub's `PRList` directly. Rejected because its status hooks, payloads, and
|
|
task associations are GitHub-specific rather than a reusable plugin contract.
|
|
- Permit each provider to choose its own dashboard actions. Rejected for first-party-
|
|
parity code hosts because it makes task creation and review placement inconsistent.
|
|
- Put shared React components in a dedicated repository/package. Rejected because it
|
|
would split host UI contexts and versioning; only pure types/helpers may be published
|
|
later if repeated plugin-authoring demand justifies them.
|