@rightkit/onboarding 0.2.0 → 0.2.1
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 +33 -1
- package/dist/Onboarding.d.ts +1 -1
- package/dist/Onboarding.js +47 -6
- package/dist/types.d.ts +15 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -27,7 +27,39 @@ import "@rightkit/onboarding/onboarding.css";
|
|
|
27
27
|
| `modelsIntro` | App copy shown at the top of the models step. |
|
|
28
28
|
| `allowSkip` | Show "Set up later" (default `true`). |
|
|
29
29
|
| `titlebar` | Node rendered in a strip along the top. |
|
|
30
|
-
| `dragRegion` | Add `data-tauri-drag-region` to that strip (default `false`; height `--rk-onboarding-titlebar-h`, default 32px). |
|
|
30
|
+
| `dragRegion` | Add `data-tauri-drag-region` to that strip on the setup steps (default `false`; height `--rk-onboarding-titlebar-h`, default 32px). Never rendered on the legal stage. |
|
|
31
|
+
| `legalDragRegion` | Also render the drag region on the legal stage (default `false`; the legal gate has no strip, and `titlebar` content alone never adds the attribute). |
|
|
32
|
+
| `legalReceipt` | `{ read(), write(record) }`: the app's own acceptance receipt. See below. |
|
|
33
|
+
|
|
34
|
+
## App-owned legal receipt
|
|
35
|
+
|
|
36
|
+
An app that already persists its own acceptance receipt passes `legalReceipt` and keeps that store as the source of truth. `read()` runs on load (sync or async) and returns a `LegalAcceptanceRecord` (`version`, `documentsHash`, `acceptedAt`, `eligibilityBasis`) or `null`. Its result replaces any acceptance in the settings backend: `null`, a throw, or an incomplete record all mean "not accepted", so the gate shows. `requiredVersion` / `isAccepted` then evaluate against the app's receipt, and a receipt for another version re-prompts with `variant: "re-acceptance"`. On agree, `write(record)` is awaited first; if it throws, the gate shows its save-failure alert and nothing else is persisted.
|
|
37
|
+
|
|
38
|
+
Mapping example for ScrapeRight's four-field `scraperight.legal-acceptance.v1` receipt (`legalAcceptanceVersion`, `legalAcceptedAt`, `legalEligibilityBasis`, `legalMaterialSha256`; pass `documentsHash={LEGAL_MATERIAL_SHA256}` so the hash round-trips):
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
const legalReceipt: LegalReceiptPort = {
|
|
42
|
+
read() {
|
|
43
|
+
const r = loadLegalAcceptance(); // reads localStorage "scraperight.legal-acceptance.v1"
|
|
44
|
+
if (!r?.legalAcceptanceVersion || !r.legalAcceptedAt || !r.legalEligibilityBasis) return null;
|
|
45
|
+
return {
|
|
46
|
+
version: r.legalAcceptanceVersion,
|
|
47
|
+
acceptedAt: r.legalAcceptedAt,
|
|
48
|
+
eligibilityBasis: r.legalEligibilityBasis, // "individual" | "enterprise"
|
|
49
|
+
documentsHash: r.legalMaterialSha256 ?? "",
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
write(rec) {
|
|
53
|
+
saveLegalAcceptance({
|
|
54
|
+
legalAcceptanceVersion: rec.version,
|
|
55
|
+
legalAcceptedAt: rec.acceptedAt, // UTC ISO, ends in Z
|
|
56
|
+
legalEligibilityBasis: rec.eligibilityBasis,
|
|
57
|
+
legalMaterialSha256: rec.documentsHash,
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
<Onboarding legalReceipt={legalReceipt} requiredVersion={REQUIRED_LEGAL_ACCEPTANCE_VERSION} documentsHash={LEGAL_MATERIAL_SHA256} ... />
|
|
62
|
+
```
|
|
31
63
|
|
|
32
64
|
## Custom steps and readiness
|
|
33
65
|
|
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 }: 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, modelsIntro, allowSkip, titlebar, dragRegion, legalDragRegion, legalReceipt }: OnboardingProps): import("react").JSX.Element;
|
package/dist/Onboarding.js
CHANGED
|
@@ -31,6 +31,13 @@ function readState(value) {
|
|
|
31
31
|
...(legalAcceptance ? { legalAcceptance } : {}),
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
|
+
/** Replace the acceptance in `state` with the one from the app's receipt (`undefined`: not accepted). */
|
|
35
|
+
function withReceipt(state, record) {
|
|
36
|
+
const { legalAcceptance: _drop, eligibilityBasis: _basis, ...rest } = state;
|
|
37
|
+
void _drop;
|
|
38
|
+
void _basis;
|
|
39
|
+
return record ? { ...rest, legalAccepted: true, eligibilityBasis: record.eligibilityBasis, legalAcceptance: record } : { ...rest, legalAccepted: false };
|
|
40
|
+
}
|
|
34
41
|
/** True when the stored acceptance satisfies the host's requirement. */
|
|
35
42
|
export function isLegalAccepted(state, requiredVersion, custom) {
|
|
36
43
|
if (custom)
|
|
@@ -66,7 +73,7 @@ function TitleStrip({ titlebar, dragRegion }) {
|
|
|
66
73
|
return null;
|
|
67
74
|
return _jsx("div", { className: "rk-onboarding__titlebar", "data-testid": "onboarding-titlebar", ...(dragRegion ? { "data-tauri-drag-region": "" } : {}), children: titlebar });
|
|
68
75
|
}
|
|
69
|
-
export function Onboarding({ settings, legal, steps = [], permissions, models, license, onComplete, onSkip, className, requiredVersion, isAccepted, documentsHash, beforeLegalGate, requireModel, autoStartDownload, modelsIntro, allowSkip = true, titlebar, dragRegion }) {
|
|
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 }) {
|
|
70
77
|
const [state, setState] = useState(null);
|
|
71
78
|
const [saving, setSaving] = useState(false);
|
|
72
79
|
const [error, setError] = useState(null);
|
|
@@ -100,9 +107,34 @@ export function Onboarding({ settings, legal, steps = [], permissions, models, l
|
|
|
100
107
|
}
|
|
101
108
|
}, [persist]);
|
|
102
109
|
const saveStep = useCallback(async (patch) => { await save(patch); }, [save]);
|
|
103
|
-
useEffect(() => {
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
let live = true;
|
|
112
|
+
void (async () => {
|
|
113
|
+
try {
|
|
114
|
+
const loaded = readState((await settings.load()).values[KEY]);
|
|
115
|
+
if (!legalReceipt) {
|
|
116
|
+
if (live)
|
|
117
|
+
setState(loaded);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// The app's receipt is the source of truth for acceptance; an unreadable receipt counts as none.
|
|
121
|
+
let record;
|
|
122
|
+
try {
|
|
123
|
+
record = readAcceptance(await legalReceipt.read());
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
record = undefined;
|
|
127
|
+
}
|
|
128
|
+
if (live)
|
|
129
|
+
setState(withReceipt(loaded, record));
|
|
130
|
+
}
|
|
131
|
+
catch (e) {
|
|
132
|
+
if (live)
|
|
133
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
134
|
+
}
|
|
135
|
+
})();
|
|
136
|
+
return () => { live = false; };
|
|
137
|
+
}, [settings, legalReceipt]);
|
|
106
138
|
const builtins = useMemo(() => [
|
|
107
139
|
{ id: "legal", label: "Agreement", title: "Review agreement", skippable: false, render: () => _jsx("p", { className: "rk-onboarding__muted", children: "Agreement accepted." }) },
|
|
108
140
|
...(permissions ? [permissionsStep(permissions)] : []),
|
|
@@ -162,10 +194,19 @@ export function Onboarding({ settings, legal, steps = [], permissions, models, l
|
|
|
162
194
|
return _jsx("div", { className: cx("rk-onboarding", className), "aria-busy": "true", children: _jsx("p", { className: "rk-onboarding__muted", children: "Loading setup\u2026" }) });
|
|
163
195
|
if (needsGate) {
|
|
164
196
|
if (!gateCleared)
|
|
165
|
-
return _jsxs("div", { className: cx("rk-onboarding", className), "aria-busy": "true", children: [_jsx(TitleStrip, { titlebar: titlebar, dragRegion:
|
|
197
|
+
return _jsxs("div", { className: cx("rk-onboarding", className), "aria-busy": "true", children: [_jsx(TitleStrip, { titlebar: titlebar, dragRegion: legalDragRegion }), _jsx("p", { className: "rk-onboarding__muted", children: "Loading setup\u2026" })] });
|
|
166
198
|
const reacceptance = activeState.legalAccepted || activeState.legalAcceptance !== undefined;
|
|
167
|
-
return _jsxs("div", { className: cx("rk-onboarding rk-onboarding--legal", className), "data-testid": "onboarding-legal", "data-variant": reacceptance ? "re-acceptance" : "first-run", children: [_jsx(TitleStrip, { titlebar: titlebar, dragRegion:
|
|
199
|
+
return _jsxs("div", { className: cx("rk-onboarding rk-onboarding--legal", className), "data-testid": "onboarding-legal", "data-variant": reacceptance ? "re-acceptance" : "first-run", children: [_jsx(TitleStrip, { titlebar: titlebar, dragRegion: legalDragRegion }), _jsx(LegalGate, { ...legal, variant: reacceptance ? "re-acceptance" : "first-run", onQuit: () => void legal.onQuit?.(), onAgree: async ({ eligibilityBasis }) => {
|
|
168
200
|
const legalAcceptance = { version: requiredVersion ?? "1", documentsHash: documentsHash ?? await hashDocuments(legal.documents), acceptedAt: new Date().toISOString(), eligibilityBasis };
|
|
201
|
+
if (legalReceipt) {
|
|
202
|
+
try {
|
|
203
|
+
await legalReceipt.write(legalAcceptance);
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
207
|
+
throw e;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
169
210
|
const progress = reacceptance ? {} : { completed: ["legal"], currentStep: builtins.length > 1 ? 1 : 0 };
|
|
170
211
|
await persist({ legalAccepted: true, eligibilityBasis, legalAcceptance, ...progress });
|
|
171
212
|
if (!reacceptance && builtins.length === 1)
|
package/dist/types.d.ts
CHANGED
|
@@ -66,6 +66,16 @@ export interface LegalAcceptanceRecord {
|
|
|
66
66
|
acceptedAt: string;
|
|
67
67
|
eligibilityBasis: EligibilityBasis;
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* App-owned persistence for the legal acceptance. Lets an app keep its existing receipt (any shape, any store) and map it
|
|
71
|
+
* to and from the package's record. When supplied it is the source of truth: `read()` is consulted on load and overrides
|
|
72
|
+
* whatever acceptance the settings backend holds (`null` means "not accepted", so the gate shows), and `write()` is
|
|
73
|
+
* awaited when the user agrees, before the settings backend is patched.
|
|
74
|
+
*/
|
|
75
|
+
export interface LegalReceiptPort {
|
|
76
|
+
read(): LegalAcceptanceRecord | null | Promise<LegalAcceptanceRecord | null>;
|
|
77
|
+
write(record: LegalAcceptanceRecord): void | Promise<void>;
|
|
78
|
+
}
|
|
69
79
|
export interface PersistedOnboardingState {
|
|
70
80
|
version: 1;
|
|
71
81
|
currentStep: number;
|
|
@@ -133,7 +143,11 @@ export interface OnboardingProps {
|
|
|
133
143
|
allowSkip?: boolean;
|
|
134
144
|
/** Optional titlebar content rendered in a strip along the top. */
|
|
135
145
|
titlebar?: ReactNode;
|
|
136
|
-
/** Render a `data-tauri-drag-region` strip at the top. Default false. */
|
|
146
|
+
/** Render a `data-tauri-drag-region` strip at the top of the setup steps. Default false. Never applied to the legal stage; see `legalDragRegion`. */
|
|
137
147
|
dragRegion?: boolean;
|
|
148
|
+
/** Also render the drag region on the legal stage (loading and gate). Default false: the legal gate has no strip. */
|
|
149
|
+
legalDragRegion?: boolean;
|
|
150
|
+
/** Read and write the legal acceptance in the app's own receipt instead of (overriding) the settings backend's copy. */
|
|
151
|
+
legalReceipt?: LegalReceiptPort;
|
|
138
152
|
}
|
|
139
153
|
export type { LegalGateDocument, LegalGateTheme };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rightkit/onboarding",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
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",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@rightkit/app-shell": "0.2.0",
|
|
33
|
-
"@rightkit/legal-ui": "0.1.
|
|
33
|
+
"@rightkit/legal-ui": "0.1.3",
|
|
34
34
|
"@testing-library/jest-dom": "^6.6.3",
|
|
35
35
|
"@testing-library/react": "^16.1.0",
|
|
36
36
|
"@testing-library/user-event": "^14.5.2",
|