A self-paced onboarding track that introduces a new employee to every KB theme in breadth (not depth), so they grasp how Respellion works day to day and week to week. Offered as a CTA inside the Dashboard "New here?" explainer card; always available regardless of enrollment. Design: - Theme is the trackable unit; the 5 "days" are a read-time presentation grouping, so re-chunking never loses progress. Completion is stored per theme in onboarding_completions. - Per-theme overview generated lazily on first open (fast-tier emit_onboarding_overview tool), cached in onboarding_overviews keyed by theme + a topics_fingerprint that triggers regeneration when the theme's topic set changes. - Reachable via /onboarding-track using the existing skipEnrollmentGate prop, decoupled from the 26-week curriculum (distinct from /onboarding, the enrollment page). Backend: - pb_migrations/1781200000_created_onboarding.js: two collections with authenticated-only rules and unique indexes; TEXT team_member_id (no relation) per the post-#18/#27 convention. Mirrored in scripts/setup-pb-collections.mjs. - src/lib/onboardingService.js: pure helpers (orderThemes, distributeThemesIntoDays, computeTopicsFingerprint, computeOnboardingProgress, buildOnboardingPlan) + generation + I/O. - db.js onboarding helpers use pb.filter() bindings (theme is free text). - LLM tool + Zod schema + registry + simulation stub. Frontend: - src/pages/OnboardingTrack.jsx (day list, per-theme overview, completion banner, progress ring/day bar). - Dashboard "New here?" card CTA + X/5-days progress chip (hidden when the KB has no themes). Docs: data-model, generation-spec (§D), frontend-spec updated. Verified: 22 new unit tests (npm test 134/134), eslint clean on changed files, npm run build OK, PocketBase v0.30.4 boot applies the migration (collections + unique indexes + authed rules confirmed), and a backend contract check (upsert idempotency, unique-index guard, special-char theme filtering). Closes #30 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
190 lines
5.0 KiB
JavaScript
190 lines
5.0 KiB
JavaScript
/// <reference path="../pb_data/types.d.ts" />
|
|
//
|
|
// Issue #30 — Onboarding track (5-day, theme-level introduction).
|
|
//
|
|
// Two collections:
|
|
// onboarding_overviews — cached per-theme overview content (one row per theme),
|
|
// keyed by the theme name, with a topics_fingerprint for
|
|
// staleness detection / regeneration.
|
|
// onboarding_completions — per-user, per-theme completion marker.
|
|
//
|
|
// Design notes (consistent with issues #18/#27):
|
|
// * team_member_id is a plain TEXT field, NOT a relation — admins can delete
|
|
// team_members, and a relation with cascadeDelete would otherwise block that
|
|
// delete / drag the collection into the migration graph. Completions are keyed
|
|
// by (team_member_id, theme); orphan rows after a member delete are harmless.
|
|
// * API rules are set EXPLICITLY to authenticated-only. The tighten-rules
|
|
// migration (1781000001) already ran against the collections that existed then,
|
|
// so new collections must lock themselves down or they default to public.
|
|
const AUTHED = '@request.auth.id != ""';
|
|
|
|
migrate((app) => {
|
|
const overviews = new Collection({
|
|
"id": "pbc_onboarding_overviews0",
|
|
"name": "onboarding_overviews",
|
|
"type": "base",
|
|
"system": false,
|
|
"fields": [
|
|
{
|
|
"autogeneratePattern": "[a-z0-9]{15}",
|
|
"hidden": false,
|
|
"id": "text_id_oov",
|
|
"max": 15,
|
|
"min": 15,
|
|
"name": "id",
|
|
"pattern": "^[a-z0-9]+$",
|
|
"presentable": false,
|
|
"primaryKey": true,
|
|
"required": true,
|
|
"system": true,
|
|
"type": "text"
|
|
},
|
|
{
|
|
"system": false,
|
|
"id": "text_theme_oov",
|
|
"name": "theme",
|
|
"type": "text",
|
|
"required": true,
|
|
"presentable": false,
|
|
"max": 0,
|
|
"min": 0,
|
|
"pattern": ""
|
|
},
|
|
{
|
|
"system": false,
|
|
"id": "json_content_oov",
|
|
"name": "content",
|
|
"type": "json",
|
|
"required": true,
|
|
"presentable": false
|
|
},
|
|
{
|
|
"system": false,
|
|
"id": "text_fingerprint_oov",
|
|
"name": "topics_fingerprint",
|
|
"type": "text",
|
|
"required": true,
|
|
"presentable": false,
|
|
"max": 0,
|
|
"min": 0,
|
|
"pattern": ""
|
|
},
|
|
{
|
|
"hidden": false,
|
|
"id": "autodate_created_oov",
|
|
"name": "created",
|
|
"onCreate": true,
|
|
"onUpdate": false,
|
|
"presentable": false,
|
|
"system": true,
|
|
"type": "autodate"
|
|
},
|
|
{
|
|
"hidden": false,
|
|
"id": "autodate_updated_oov",
|
|
"name": "updated",
|
|
"onCreate": true,
|
|
"onUpdate": true,
|
|
"presentable": false,
|
|
"system": true,
|
|
"type": "autodate"
|
|
}
|
|
],
|
|
"indexes": [
|
|
"CREATE UNIQUE INDEX `idx_onboarding_overviews_theme` ON `onboarding_overviews` (`theme`)"
|
|
],
|
|
"listRule": AUTHED,
|
|
"viewRule": AUTHED,
|
|
"createRule": AUTHED,
|
|
"updateRule": AUTHED,
|
|
"deleteRule": AUTHED
|
|
});
|
|
|
|
app.save(overviews);
|
|
|
|
const completions = new Collection({
|
|
"id": "pbc_onboarding_completions0",
|
|
"name": "onboarding_completions",
|
|
"type": "base",
|
|
"system": false,
|
|
"fields": [
|
|
{
|
|
"autogeneratePattern": "[a-z0-9]{15}",
|
|
"hidden": false,
|
|
"id": "text_id_ocp",
|
|
"max": 15,
|
|
"min": 15,
|
|
"name": "id",
|
|
"pattern": "^[a-z0-9]+$",
|
|
"presentable": false,
|
|
"primaryKey": true,
|
|
"required": true,
|
|
"system": true,
|
|
"type": "text"
|
|
},
|
|
{
|
|
"system": false,
|
|
"id": "text_ocp_team_member",
|
|
"name": "team_member_id",
|
|
"type": "text",
|
|
"required": true,
|
|
"presentable": false,
|
|
"max": 0,
|
|
"min": 0,
|
|
"pattern": ""
|
|
},
|
|
{
|
|
"system": false,
|
|
"id": "text_ocp_theme",
|
|
"name": "theme",
|
|
"type": "text",
|
|
"required": true,
|
|
"presentable": false,
|
|
"max": 0,
|
|
"min": 0,
|
|
"pattern": ""
|
|
},
|
|
{
|
|
"hidden": false,
|
|
"id": "autodate_created_ocp",
|
|
"name": "created",
|
|
"onCreate": true,
|
|
"onUpdate": false,
|
|
"presentable": false,
|
|
"system": true,
|
|
"type": "autodate"
|
|
},
|
|
{
|
|
"hidden": false,
|
|
"id": "autodate_updated_ocp",
|
|
"name": "updated",
|
|
"onCreate": true,
|
|
"onUpdate": true,
|
|
"presentable": false,
|
|
"system": true,
|
|
"type": "autodate"
|
|
}
|
|
],
|
|
"indexes": [
|
|
"CREATE UNIQUE INDEX `idx_onboarding_completions_user_theme` ON `onboarding_completions` (`team_member_id`, `theme`)"
|
|
],
|
|
"listRule": AUTHED,
|
|
"viewRule": AUTHED,
|
|
"createRule": AUTHED,
|
|
"updateRule": AUTHED,
|
|
"deleteRule": AUTHED
|
|
});
|
|
|
|
app.save(completions);
|
|
|
|
}, (app) => {
|
|
const completions = app.findCollectionByNameOrId("onboarding_completions");
|
|
if (completions) {
|
|
app.delete(completions);
|
|
}
|
|
const overviews = app.findCollectionByNameOrId("onboarding_overviews");
|
|
if (overviews) {
|
|
app.delete(overviews);
|
|
}
|
|
})
|