feat: full screen onboarding with a plugin picker and per step versions

This commit is contained in:
LukeGus committed 2026-10-06 17:44:25 -05:00
1 parent 1e74ce4827
commit b9dd3488a5
59 files changed
+5413 -643

No files matched your search

+71 -1
View File
@@ -694,7 +694,11 @@ export interface ActionSlotDefinition {
export interface SlotContribution {
actionId: string;
titleKey: string;
/** Secondary text, for slots that show one (onboarding cards). */
/**
* Secondary text, for slots that show one. The onboarding.* slots that
* used it are deprecated: use registerOnboardingStep. The feature and
* workflow card slots are no longer shown.
*/
descriptionKey?: string;
icon?: IconComponent;
kind?: "button" | "component" | string;
@@ -705,6 +709,65 @@ export interface SlotContribution {
order?: number;
}
export type OnboardingMode = "full" | "partial" | "rerun";
/**
* Where a plugin step sits in onboarding. "setup" comes right after the
* appearance steps, for things that need doing before the feature works
* (like pointing at a guacd server). "explore" (the default) comes after
* that, "security" after the account security step. Onboarding is for
* choices, not for explaining features.
*/
export type OnboardingSection = "setup" | "explore" | "security";
export interface OnboardingStepProps {
/**
* "full" is a new user's first run, "partial" shows only steps they have
* not seen yet (a plugin installed later, a step whose version went up),
* "rerun" is the user running setup again from settings.
*/
mode: OnboardingMode;
isAdmin: boolean;
isDesktop: boolean;
/** Blocks or allows Next. Steps can continue by default. */
setCanContinue: (ok: boolean) => void;
/**
* Runs when Next is pressed, before moving on. Return false to stay on the
* step (to show an error, say). Pass null to clear it.
*/
setBeforeNext: (fn: (() => boolean | Promise<boolean>) | null) => void;
/**
* False on a first run, which shows before the app shell exists: app.tabs
* and the shell callbacks do nothing until it is true.
*/
shellReady: boolean;
}
export interface OnboardingStepContribution {
/** Unique within the plugin. Lowercase letters, digits and dashes. */
id: string;
/**
* Starts at 1. Raise it when the step changes enough that people who saw
* it should see it again; they get just this step, not all of onboarding.
*/
version?: number;
titleKey: string;
descriptionKey?: string;
icon?: IconComponent;
component: ComponentType<OnboardingStepProps>;
/** "admin" shows it only to users who can manage plugins. */
audience?: "all" | "admin";
/** A permission the user must hold, like registerAction's. */
permission?: string;
section?: OnboardingSection;
order?: number;
when?: (context: {
isAdmin: boolean;
isDesktop: boolean;
mode: OnboardingMode;
}) => boolean;
}
export interface SshAuthEditorProps {
form: Record<string, unknown>;
setField: (key: string, value: unknown) => void;
@@ -894,6 +957,13 @@ export interface TermixApp extends TermixAppInfo {
slotId: string,
contribution: SlotContribution,
) => Disposer;
/**
* Adds a step to onboarding. New users see it in their first run; people
* who already finished onboarding see just this step the next time they
* open Termix (or right away, if they install the plugin mid-session).
* Needs plugin API 1.1.
*/
registerOnboardingStep: (step: OnboardingStepContribution) => Disposer;
invokeAction: (id: string, ...args: unknown[]) => Promise<unknown>;
/**
* Offers a component to other plugins and to core by id, rendered where
+2 -1
View File
@@ -21,7 +21,7 @@ import { isKnownCapability } from "./capabilities.js";
* "^1.2" needs 1.2 or later). engine.termix is checked against the core
* version by the server.
*/
export const PLUGIN_API_VERSION = "1.0.0";
export const PLUGIN_API_VERSION = "1.1.0";
/** The API major, the default engine.api for a new plugin. */
export const SUPPORTED_PLUGIN_API_VERSION = String(
@@ -81,6 +81,7 @@ export const RESERVED_PLUGIN_IDS: readonly string[] = [
"termix",
"plugin",
"plugins",
"onboarding",
"users",
"system",
"sdk",
+15
View File
@@ -2139,6 +2139,8 @@ export interface RenderedPluginApp {
paletteGroups: () => string[];
keybindingActions: () => string[];
keybindingDefaults: () => string[];
/** Onboarding step ids, without the plugin prefix. */
onboardingSteps: () => string[];
};
/** A registered palette group's items, as the palette loads them on open. */
loadPaletteGroup: (
@@ -2160,6 +2162,19 @@ export interface RenderedPluginApp {
renderTab: (type: string, props?: Record<string, unknown>) => HTMLElement;
renderPanel: (id: string, props?: Record<string, unknown>) => HTMLElement;
renderDashboardCard: (id: string) => HTMLElement;
/**
* Renders an onboarding step with OnboardingStepProps for a first run,
* overridable. Read what the step set with the returned `canContinue()`
* and `next()` (runs its beforeNext, true when it has none).
*/
renderOnboardingStep: (
id: string,
props?: Record<string, unknown>,
) => {
element: HTMLElement;
canContinue: () => boolean;
next: () => Promise<boolean>;
};
/** Renders one component of a contributed extension, "view" by default. */
renderExtension: (
pointId: string,