mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-06 16:34:36 +00:00
feat(plugin): let users select a sidebar view
An exclusive view replaces the sidebar content while the host retains navigation, callouts, and footer. The device remembers the selection, unavailable views fall back to the default, and throwing views provide recovery. Device-scoped plugin settings persist locally with schema validation and revision conflicts. Web E2E and Android tablet QA cover selection, preserved chrome, disabled-view fallback, and error recovery.
This commit is contained in:
@@ -393,7 +393,7 @@ describe("sidebar data SDK boundary", () => {
|
||||
it("accepts references and preserves string-id panel consumers", () => {
|
||||
expect(
|
||||
typeErrors(`
|
||||
import { useWorkspace, useAgent, type WorkspaceRef, type AgentRef, useProject, useTerminal, useWorkspaces, useAgents, useProjects, useTerminals, type ProjectRef, type TerminalRef, type Query, type Group } from "@getpaseo/plugin/client";
|
||||
import { useWorkspace, useAgent, type PluginClientContext, type PluginSidebarViewProps, type WorkspaceRef, type AgentRef, useProject, useTerminal, useWorkspaces, useAgents, useProjects, useTerminals, type ProjectRef, type TerminalRef, type Query, type Group } from "@getpaseo/plugin/client";
|
||||
import type { PluginWorkspaceSnapshot } from "@getpaseo/plugin";
|
||||
function Panel({ workspace, agent }: { workspace: WorkspaceRef; agent: AgentRef }) {
|
||||
const snapshot: PluginWorkspaceSnapshot | null = useWorkspace(workspace);
|
||||
@@ -407,6 +407,15 @@ describe("sidebar data SDK boundary", () => {
|
||||
const terminals: readonly TerminalRef[] = useTerminals();
|
||||
if (projects[0]) useProject(projects[0], item => item.displayName);
|
||||
if (terminals[0]) useTerminal(terminals[0]);
|
||||
function View(props: PluginSidebarViewProps) {
|
||||
props.navigation.openWorkspace(workspace);
|
||||
props.navigation.openAgent(agent);
|
||||
props.dismiss();
|
||||
return null;
|
||||
}
|
||||
function contribute(client: PluginClientContext) {
|
||||
client.addSidebarView({ id: "workspaces", title: "Workspaces", Component: View });
|
||||
}
|
||||
const query: Query<PluginWorkspaceSnapshot> = { hosts: [workspace.hostId], orderBy: ["name", "desc"] };
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { WorkspaceRef, AgentRef, ProjectRef, TerminalRef } from "../refs.js";
|
||||
import type { PluginText } from "../text.js";
|
||||
import type { ComponentType } from "react";
|
||||
import type { PaseoApi } from "@getpaseo/client";
|
||||
@@ -96,6 +97,24 @@ export interface PluginSidebarItemContribution<Text extends PluginText = string>
|
||||
Component: ComponentType<PluginSidebarItemProps>;
|
||||
}
|
||||
|
||||
export interface PluginSidebarViewProps extends Pick<PluginHostProps, "theme" | "layout"> {
|
||||
active: { workspace: WorkspaceRef | null; agent: AgentRef | null };
|
||||
navigation: {
|
||||
openWorkspace(ref: WorkspaceRef): void;
|
||||
openAgent(ref: AgentRef): void;
|
||||
openTerminal(ref: TerminalRef): void;
|
||||
openProject(ref: ProjectRef): void;
|
||||
};
|
||||
dismiss(): void;
|
||||
}
|
||||
|
||||
export interface PluginSidebarViewContribution<Text extends PluginText = string> {
|
||||
id: string;
|
||||
title: Text;
|
||||
description?: Text;
|
||||
Component: ComponentType<PluginSidebarViewProps>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Lucide icon name, inline SVG markup, or a component. Components receive the surface's size
|
||||
* and color; buttons widen the props with their host and target context.
|
||||
@@ -185,6 +204,7 @@ export interface PluginClientContext extends PluginCommandCapabilities, PluginCl
|
||||
playAudio(source: { base64: string; mimeType: string }): Promise<void>;
|
||||
addSettingsScreen(contribution: PluginSettingsScreenContribution<PluginText>): PluginCleanup;
|
||||
addScreen(contribution: PluginScreenContribution<PluginText>): PluginCleanup;
|
||||
addSidebarView(contribution: PluginSidebarViewContribution<PluginText>): PluginCleanup;
|
||||
addSidebarHeaderItem(contribution: PluginSidebarItemContribution<PluginText>): PluginCleanup;
|
||||
addSidebarFooterItem(contribution: PluginSidebarItemContribution<PluginText>): PluginCleanup;
|
||||
addAgentPanel(contribution: PluginAgentPanelContribution<PluginText>): PluginCleanup;
|
||||
|
||||
@@ -15,6 +15,8 @@ export type {
|
||||
PluginOpenScreenInput,
|
||||
PluginScreenLocation,
|
||||
PluginSidebarItemProps,
|
||||
PluginSidebarViewProps,
|
||||
PluginSidebarViewContribution,
|
||||
PluginSidebarItemContribution,
|
||||
PluginIcon,
|
||||
PluginPanelLocation,
|
||||
|
||||
@@ -4,10 +4,10 @@ import { defineRpc } from "./rpc.js";
|
||||
|
||||
export interface SettingsDefinition<Schema extends ZodType = ZodType> {
|
||||
id: string;
|
||||
scope: "host";
|
||||
scope: "host" | "device";
|
||||
version: number;
|
||||
schema: Schema;
|
||||
/** Convert a previous stored version to the current schema. Runs on the host. */
|
||||
/** Convert a previous stored version to the current schema. Runs in the selected scope. */
|
||||
migrate?: (values: unknown, fromVersion: number) => unknown | Promise<unknown>;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ export function defineSettings<Schema extends ZodType>(
|
||||
definition: SettingsDefinition<Schema>,
|
||||
): SettingsDefinition<Schema> {
|
||||
if (!/^[a-z][a-z0-9_-]*$/.test(definition.id)) throw new Error("Invalid settings ID");
|
||||
if (definition.scope !== "host") throw new Error("Only host-scoped settings are supported");
|
||||
if (definition.scope !== "host" && definition.scope !== "device")
|
||||
throw new Error("Invalid settings scope");
|
||||
if (!Number.isSafeInteger(definition.version) || definition.version < 1)
|
||||
throw new Error("Invalid settings version");
|
||||
return definition;
|
||||
|
||||
Reference in New Issue
Block a user