feat(web): add global app status surface (#1869)
* feat(web): add app status bar * feat(web): add app status bar visibility toggle * feat(web): persist status bar item order * fix(web): refine app status bar behavior * fix(web): polish app status bar presentation * test(web): split plugin slot coverage groups * fix(web): address status surface review feedback * fix(web): stabilize status bar overlay tests
This commit is contained in:
@@ -303,7 +303,8 @@ interface PluginRegistry {
|
||||
registerSettingsRoute(path: string, Component: React.ComponentType): void;
|
||||
// Named slot injection. Initial slots: "task-sidebar", "settings-nav",
|
||||
// "main-nav-footer", "chat-input-actions", "chat-top-bar", "main-top-bar",
|
||||
// "plugin-settings" (see "Named slots" below).
|
||||
// "app-status-bar-left", "app-status-bar-right", and "plugin-settings"
|
||||
// (see "Named slots" below).
|
||||
registerComponent(slot: string, Component: React.ComponentType<{ slotProps?: unknown }>): void;
|
||||
// WS action handler, bridged into the existing lib/ws dispatch.
|
||||
registerWsHandler(action: string, handler: (payload: unknown) => void): void;
|
||||
@@ -395,6 +396,8 @@ plugins at once. Available slots:
|
||||
| `chat-input-actions` | Chat composer toolbar, beside the model picker, mic, and send button | `{ taskId, taskTitle, activeSessionId, sessionIds }` |
|
||||
| `chat-top-bar` | Session top bar, beside the CPU/DB metrics and the document/editor/debug controls | `{ taskId, taskTitle, workspaceId, activeSessionId, sessionIds }` |
|
||||
| `main-top-bar` | Default app top bar (Home / Kanban / Tasks), beside the CPU/DB metrics and the view/display controls | `{ workspaceId, workspaceLabel, currentPage }` |
|
||||
| `app-status-bar-left` | Default-left item in the global status surface | `AppStatusBarSlotProps` |
|
||||
| `app-status-bar-right` | Default-right item in the global status surface | `AppStatusBarSlotProps` |
|
||||
| `plugin-settings` | A plugin's own settings page (**Settings > Plugins > `<plugin>`**), at the top above the settings form | `{ pluginId, status }` |
|
||||
|
||||
`plugin-settings` is the one exception to "every plugin's component renders":
|
||||
@@ -467,8 +470,7 @@ example.
|
||||
### Session top bar
|
||||
|
||||
Register a `chat-top-bar` component to surface at-a-glance status in the
|
||||
session top bar, beside the first-party CPU/DB metrics and the
|
||||
document/editor/debug controls. The host passes the current context as
|
||||
session top bar, beside first-party document/editor/debug controls. The host passes the current context as
|
||||
`slotProps`:
|
||||
|
||||
```ts
|
||||
@@ -545,6 +547,53 @@ show.
|
||||
registry.registerComponent("plugin-settings", makeSettingsStatus(host));
|
||||
```
|
||||
|
||||
### Global Status bar
|
||||
|
||||
Register `app-status-bar-left` or `app-status-bar-right` for app-wide, compact
|
||||
status UI. Kandev mounts exactly one presentation: a 24 px bar on tablet and
|
||||
desktop, or an in-flow Status drawer section on phone. Keep bar content small;
|
||||
render a touch-usable row when `presentation` is `"mobile-drawer"`.
|
||||
|
||||
```js
|
||||
function StatusContribution({ slotProps }) {
|
||||
const { placement, presentation, activeTaskId } = slotProps ?? {};
|
||||
return host.jsx(
|
||||
"span",
|
||||
{ className: presentation === "bar" ? "truncate text-xs" : "block min-h-11 px-3 py-2" },
|
||||
`${placement}: ${activeTaskId ?? "no active task"}`,
|
||||
);
|
||||
}
|
||||
|
||||
registry.registerComponent("app-status-bar-left", StatusContribution);
|
||||
registry.registerComponent("app-status-bar-right", StatusContribution);
|
||||
```
|
||||
|
||||
Each contribution receives this exact context:
|
||||
|
||||
```ts
|
||||
type AppStatusBarSlotProps = {
|
||||
placement: "left" | "right";
|
||||
presentation: "bar" | "mobile-drawer";
|
||||
density: "full" | "compact";
|
||||
pathname: string;
|
||||
activeWorkspaceId: string | null;
|
||||
activeTaskId: string | null;
|
||||
activeSessionId: string | null;
|
||||
};
|
||||
```
|
||||
|
||||
The IDs are hints; use `host.store` for full records. Each component registration
|
||||
is one opaque item: Kandev does not inspect or separately reorder its children.
|
||||
The slot chooses the default side. A user can Cmd-drag (macOS) or Ctrl-drag
|
||||
(other desktop platforms) with a mouse across the full bar, and Kandev preserves
|
||||
that backend-owned order across reloads, restarts, and plugin disable/enable.
|
||||
Phone lists the saved left sequence followed by the saved right sequence and does
|
||||
not offer drag ordering. There is no keyboard-arrow, touch, or plugin-priority
|
||||
ordering API. Enable, disable, and uninstall update the live surface without a
|
||||
reload, and each contribution has its own error boundary. A full-bleed route
|
||||
(`topbar: false`) owns its own chrome; mount the host Status trigger there if that
|
||||
route should expose Status.
|
||||
|
||||
## Three integration patterns
|
||||
|
||||
Both example repos are full, working plugins — read them rather than
|
||||
|
||||
Reference in New Issue
Block a user