@rightkit/onboarding 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/Onboarding.d.ts +1 -1
- package/dist/Onboarding.js +6 -4
- package/dist/steps.d.ts +6 -2
- package/dist/steps.js +20 -5
- package/dist/types.d.ts +20 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,6 +24,8 @@ import "@rightkit/onboarding/onboarding.css";
|
|
|
24
24
|
| `beforeLegalGate()` | Awaited before the gate shows (e.g. size or reveal the window). The package does no window sizing itself. |
|
|
25
25
|
| `requireModel` | Continue/Finish on the models step wait for an installed model. |
|
|
26
26
|
| `autoStartDownload` | Start the first missing model download when the models step opens. |
|
|
27
|
+
| `shouldAutoStart(entry, models)` | Choose which missing model auto-starts when the models step first opens (first `missing` entry returning true). Replaces `autoStartDownload`'s rule, including its "a model is already installed" guard. Once per mount, across re-renders and Back/Next. |
|
|
28
|
+
| `onModelsStepEnter(ctx)` | Full control, called once when the step opens and the list has loaded: `ctx.models`, `ctx.start(id)`, `ctx.refresh()`. Replaces `shouldAutoStart` and `autoStartDownload`; a throw is shown in the step. |
|
|
27
29
|
| `modelsIntro` | App copy shown at the top of the models step. |
|
|
28
30
|
| `allowSkip` | Show "Set up later" (default `true`). |
|
|
29
31
|
| `titlebar` | Node rendered in a strip along the top. |
|
package/dist/Onboarding.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ import type { OnboardingProps, PersistedOnboardingState } from "./types.js";
|
|
|
2
2
|
/** True when the stored acceptance satisfies the host's requirement. */
|
|
3
3
|
export declare function isLegalAccepted(state: PersistedOnboardingState, requiredVersion?: string, custom?: (state: PersistedOnboardingState) => boolean): boolean;
|
|
4
4
|
export declare function onboardingStateKey(): string;
|
|
5
|
-
export declare function Onboarding({ settings, legal, steps, permissions, models, license, onComplete, onSkip, className, requiredVersion, isAccepted, documentsHash, beforeLegalGate, requireModel, autoStartDownload, modelsIntro, allowSkip, titlebar, dragRegion, legalDragRegion, legalReceipt }: OnboardingProps): import("react").JSX.Element;
|
|
5
|
+
export declare function Onboarding({ settings, legal, steps, permissions, models, license, onComplete, onSkip, className, requiredVersion, isAccepted, documentsHash, beforeLegalGate, requireModel, autoStartDownload, shouldAutoStart, onModelsStepEnter, modelsIntro, allowSkip, titlebar, dragRegion, legalDragRegion, legalReceipt }: OnboardingProps): import("react").JSX.Element;
|
package/dist/Onboarding.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { LegalGate } from "@rightkit/legal-ui";
|
|
4
4
|
import { Button } from "@rightkit/app-shell/react";
|
|
5
5
|
import { cx } from "./ui.js";
|
|
@@ -73,12 +73,14 @@ function TitleStrip({ titlebar, dragRegion }) {
|
|
|
73
73
|
return null;
|
|
74
74
|
return _jsx("div", { className: "rk-onboarding__titlebar", "data-testid": "onboarding-titlebar", ...(dragRegion ? { "data-tauri-drag-region": "" } : {}), children: titlebar });
|
|
75
75
|
}
|
|
76
|
-
export function Onboarding({ settings, legal, steps = [], permissions, models, license, onComplete, onSkip, className, requiredVersion, isAccepted, documentsHash, beforeLegalGate, requireModel, autoStartDownload, modelsIntro, allowSkip = true, titlebar, dragRegion, legalDragRegion = false, legalReceipt }) {
|
|
76
|
+
export function Onboarding({ settings, legal, steps = [], permissions, models, license, onComplete, onSkip, className, requiredVersion, isAccepted, documentsHash, beforeLegalGate, requireModel, autoStartDownload, shouldAutoStart, onModelsStepEnter, modelsIntro, allowSkip = true, titlebar, dragRegion, legalDragRegion = false, legalReceipt }) {
|
|
77
77
|
const [state, setState] = useState(null);
|
|
78
78
|
const [saving, setSaving] = useState(false);
|
|
79
79
|
const [error, setError] = useState(null);
|
|
80
80
|
const [readyMap, setReadyMap] = useState({});
|
|
81
81
|
const [gateCleared, setGateCleared] = useState(false);
|
|
82
|
+
/** One auto-start per mount, even when the models step unmounts (Back/Next) or the step list is rebuilt. */
|
|
83
|
+
const modelsStarted = useRef(false);
|
|
82
84
|
/** Persists a patch; throws when the settings backend rejects it. */
|
|
83
85
|
const persist = useCallback(async (patch) => {
|
|
84
86
|
setSaving(true);
|
|
@@ -138,10 +140,10 @@ export function Onboarding({ settings, legal, steps = [], permissions, models, l
|
|
|
138
140
|
const builtins = useMemo(() => [
|
|
139
141
|
{ id: "legal", label: "Agreement", title: "Review agreement", skippable: false, render: () => _jsx("p", { className: "rk-onboarding__muted", children: "Agreement accepted." }) },
|
|
140
142
|
...(permissions ? [permissionsStep(permissions)] : []),
|
|
141
|
-
...(models ? [modelsStep(models, { required: requireModel, autoStartDownload, intro: modelsIntro })] : []),
|
|
143
|
+
...(models ? [modelsStep(models, { required: requireModel, autoStartDownload, shouldAutoStart, onModelsStepEnter, intro: modelsIntro, startGuard: modelsStarted })] : []),
|
|
142
144
|
...(license ? [licenseStep(license)] : []),
|
|
143
145
|
...steps,
|
|
144
|
-
], [license, models, permissions, steps, requireModel, autoStartDownload, modelsIntro]);
|
|
146
|
+
], [license, models, permissions, steps, requireModel, autoStartDownload, shouldAutoStart, onModelsStepEnter, modelsIntro]);
|
|
145
147
|
const activeState = state ?? INITIAL;
|
|
146
148
|
const accepted = isLegalAccepted(activeState, requiredVersion, isAccepted);
|
|
147
149
|
const needsGate = state !== null && !accepted;
|
package/dist/steps.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import type { LicenseBackend, ModelsBackend, OnboardingStep, PermissionsBackend } from "./types.js";
|
|
2
|
+
import type { LicenseBackend, ModelsAutoStart, ModelsBackend, OnboardingStep, PermissionsBackend } from "./types.js";
|
|
3
3
|
export declare function permissionsStep(backend: PermissionsBackend, options?: {
|
|
4
4
|
label?: string;
|
|
5
5
|
title?: string;
|
|
6
6
|
description?: string;
|
|
7
7
|
}): OnboardingStep;
|
|
8
|
-
export interface ModelsStepOptions {
|
|
8
|
+
export interface ModelsStepOptions extends ModelsAutoStart {
|
|
9
9
|
label?: string;
|
|
10
10
|
title?: string;
|
|
11
11
|
description?: string;
|
|
@@ -15,6 +15,10 @@ export interface ModelsStepOptions {
|
|
|
15
15
|
autoStartDownload?: boolean;
|
|
16
16
|
/** App copy rendered at the top of the step. */
|
|
17
17
|
intro?: ReactNode;
|
|
18
|
+
/** Shared "already auto-started" flag. `Onboarding` supplies one that survives Back/Next and re-renders; omit it and the guard lives for the step's mount only. */
|
|
19
|
+
startGuard?: {
|
|
20
|
+
current: boolean;
|
|
21
|
+
};
|
|
18
22
|
}
|
|
19
23
|
export declare function modelsStep(backend: ModelsBackend, options?: ModelsStepOptions): OnboardingStep;
|
|
20
24
|
export declare function licenseStep(backend: LicenseBackend, options?: {
|
package/dist/steps.js
CHANGED
|
@@ -29,12 +29,13 @@ function PermissionsBody({ backend }) {
|
|
|
29
29
|
export function permissionsStep(backend, options = {}) {
|
|
30
30
|
return { id: "permissions", label: options.label ?? "Permissions", title: options.title ?? "Allow what this app needs", description: options.description ?? "You can change these permissions later in System Settings.", skippable: true, render: () => _jsx(PermissionsBody, { backend: backend }) };
|
|
31
31
|
}
|
|
32
|
-
function ModelsBody({ backend, onReady, autoStartDownload, intro }) {
|
|
32
|
+
function ModelsBody({ backend, onReady, autoStartDownload, shouldAutoStart, onModelsStepEnter, intro, startGuard }) {
|
|
33
33
|
const [models, setModels] = useState([]);
|
|
34
34
|
const [loaded, setLoaded] = useState(false);
|
|
35
35
|
const [busy, setBusy] = useState(null);
|
|
36
36
|
const [error, setError] = useState(null);
|
|
37
|
-
const
|
|
37
|
+
const ownGuard = useRef(false);
|
|
38
|
+
const autoStarted = startGuard ?? ownGuard;
|
|
38
39
|
const reload = useCallback(() => backend.list().then((list) => { setModels(list); setLoaded(true); }, (e) => setError(e instanceof Error ? e.message : String(e))), [backend]);
|
|
39
40
|
useEffect(() => {
|
|
40
41
|
void reload();
|
|
@@ -56,9 +57,23 @@ function ModelsBody({ backend, onReady, autoStartDownload, intro }) {
|
|
|
56
57
|
void request.then(reload, (e) => setError(e instanceof Error ? e.message : String(e))).finally(() => setBusy(null));
|
|
57
58
|
};
|
|
58
59
|
useEffect(() => {
|
|
59
|
-
if (!
|
|
60
|
+
if (!loaded || autoStarted.current)
|
|
61
|
+
return;
|
|
62
|
+
if (!onModelsStepEnter && !shouldAutoStart && !autoStartDownload)
|
|
60
63
|
return;
|
|
61
64
|
autoStarted.current = true;
|
|
65
|
+
if (onModelsStepEnter) {
|
|
66
|
+
// The app owns which model starts and when; the hook runs once per Onboarding mount.
|
|
67
|
+
const ctx = { models, start: (id) => backend.install(id).then(reload), refresh: () => backend.list().then((list) => { setModels(list); return list; }) };
|
|
68
|
+
void Promise.resolve().then(() => onModelsStepEnter(ctx)).catch((e) => setError(e instanceof Error ? e.message : String(e)));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (shouldAutoStart) {
|
|
72
|
+
const pick = models.find((m) => m.state === "missing" && shouldAutoStart(m, models));
|
|
73
|
+
if (pick)
|
|
74
|
+
run(pick.id, backend.install(pick.id));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
62
77
|
if (models.some((m) => m.state === "installed" || m.state === "downloading" || m.state === "verifying"))
|
|
63
78
|
return;
|
|
64
79
|
const first = models.find((m) => m.state === "missing");
|
|
@@ -66,7 +81,7 @@ function ModelsBody({ backend, onReady, autoStartDownload, intro }) {
|
|
|
66
81
|
run(first.id, backend.install(first.id));
|
|
67
82
|
// run is intentionally omitted: it only closes over setters.
|
|
68
83
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
69
|
-
}, [autoStartDownload, loaded, models, backend]);
|
|
84
|
+
}, [autoStartDownload, shouldAutoStart, onModelsStepEnter, loaded, models, backend]);
|
|
70
85
|
return (_jsxs("div", { className: "rk-onboarding__card", "data-testid": "onboarding-models", children: [intro ? _jsx("div", { className: "rk-onboarding__intro", "data-testid": "onboarding-models-intro", children: intro }) : null, error ? _jsx("p", { className: "rk-onboarding__error", role: "alert", children: error }) : null, models.map((model) => {
|
|
71
86
|
const progress = model.progress ?? (model.state === "installed" ? 1 : 0);
|
|
72
87
|
const active = model.state === "downloading" || model.state === "verifying";
|
|
@@ -82,7 +97,7 @@ export function modelsStep(backend, options = {}) {
|
|
|
82
97
|
id: "models", label: options.label ?? "Models", title: options.title ?? "Choose local models", description: options.description ?? "Downloads are verified before a model is activated.", skippable: true,
|
|
83
98
|
canContinue: options.required ? (ctx) => ctx.ready.models === true : undefined,
|
|
84
99
|
blockedHint: options.required ? "Download a model to continue." : undefined,
|
|
85
|
-
render: (ctx) => _jsx(ModelsBody, { backend: backend, onReady: ctx.setReady, autoStartDownload: options.autoStartDownload, intro: options.intro }),
|
|
100
|
+
render: (ctx) => _jsx(ModelsBody, { backend: backend, onReady: ctx.setReady, autoStartDownload: options.autoStartDownload, shouldAutoStart: options.shouldAutoStart, onModelsStepEnter: options.onModelsStepEnter, intro: options.intro, startGuard: options.startGuard }),
|
|
86
101
|
};
|
|
87
102
|
}
|
|
88
103
|
function LicenseBody({ backend }) {
|
package/dist/types.d.ts
CHANGED
|
@@ -135,8 +135,12 @@ export interface OnboardingProps {
|
|
|
135
135
|
beforeLegalGate?: () => void | Promise<void>;
|
|
136
136
|
/** Require at least one downloaded model before Continue/Finish on the models step. Default false. */
|
|
137
137
|
requireModel?: boolean;
|
|
138
|
-
/** Start downloading the first missing model when the models step opens. Default false. */
|
|
138
|
+
/** Start downloading the first missing model when the models step opens (skipped when any model is installed or downloading). Default false. */
|
|
139
139
|
autoStartDownload?: boolean;
|
|
140
|
+
/** Choose which missing model starts when the models step first opens: the first `state === "missing"` entry for which this returns true. Replaces the `autoStartDownload` rule (no "something is installed" guard; the app decides). Fires at most once per `Onboarding` mount, across re-renders and Back/Next. */
|
|
141
|
+
shouldAutoStart?: (entry: ModelEntry, models: readonly ModelEntry[]) => boolean;
|
|
142
|
+
/** Full control: called once, when the models step first opens and the list has loaded. Replaces `shouldAutoStart` and `autoStartDownload`. Start models through `ctx.start`. Fires at most once per `Onboarding` mount; a throw is shown in the step. */
|
|
143
|
+
onModelsStepEnter?: (ctx: ModelsStepEnterContext) => void | Promise<void>;
|
|
140
144
|
/** App copy shown at the top of the models step. */
|
|
141
145
|
modelsIntro?: ReactNode;
|
|
142
146
|
/** Show "Set up later". Default true. */
|
|
@@ -151,3 +155,18 @@ export interface OnboardingProps {
|
|
|
151
155
|
legalReceipt?: LegalReceiptPort;
|
|
152
156
|
}
|
|
153
157
|
export type { LegalGateDocument, LegalGateTheme };
|
|
158
|
+
/** Context handed to `onModelsStepEnter`. */
|
|
159
|
+
export interface ModelsStepEnterContext {
|
|
160
|
+
/** The model list as loaded when the step opened. */
|
|
161
|
+
models: readonly ModelEntry[];
|
|
162
|
+
/** Start installing one model, then refresh the list. */
|
|
163
|
+
start(id: string): Promise<void>;
|
|
164
|
+
/** Re-read the list from the backend (and update the step). */
|
|
165
|
+
refresh(): Promise<ModelEntry[]>;
|
|
166
|
+
}
|
|
167
|
+
/** The auto-start options shared by `Onboarding` and `modelsStep`. */
|
|
168
|
+
export interface ModelsAutoStart {
|
|
169
|
+
autoStartDownload?: boolean;
|
|
170
|
+
shouldAutoStart?: (entry: ModelEntry, models: readonly ModelEntry[]) => boolean;
|
|
171
|
+
onModelsStepEnter?: (ctx: ModelsStepEnterContext) => void | Promise<void>;
|
|
172
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rightkit/onboarding",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "First-run onboarding framework: ordered steps, progress, skip/resume, legal gate first, permissions, model download and licence/trial steps.",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
6
|
"type": "module",
|