Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message
kinetix-onboarding
kinetix-onboarding
happones/kinetix
claude-code
onboarding
checklist
inertia
vue-components
composables
steps
teams

First-run onboarding: a backend-driven setup checklist (auto/manual steps, per-user progress), a reusable empty-state block, and a dependency-free product tour. Activates when declaring checklist steps, mounting the checklist/empty-state/tour, or wiring first-run UX.

Install
php artisan boost:add-skill happones/kinetix

Save this content to: .claude/skills/kinetix-onboarding/SKILL.md

# Kinetix Onboarding Development

## When to Apply

Activate this skill when:
- Declaring checklist steps (`KinetixOnboarding::step`) or configuring `kinetix.onboarding`.
- Mounting `<KinetixOnboardingChecklist>` / `<KinetixEmptyState>` / `<KinetixTour>`.
- Using `useKinetixOnboarding` or `useKinetixTour`.

## Documentation

For full details, reference `docs/onboarding.md` (published at https://happones.github.io/kinetix/onboarding).

## Configuration

```bash
php artisan vendor:publish --tag=kinetix-onboarding-migrations
php artisan migrate
```

```php
'onboarding' => [
    'enabled' => env('KINETIX_ONBOARDING_ENABLED', false),
    'share'   => env('KINETIX_ONBOARDING_SHARE', true),  // state on every Inertia response
    'teams'   => env('KINETIX_ONBOARDING_TEAMS', false), // per-team vs per-user progress
],
```

---

## Checklist (backend-driven)

```php
use Happones\Kinetix\Onboarding\KinetixOnboarding;

KinetixOnboarding::step('verify-email', 'Verify your email')
    ->description('…')->cta('Resend', '/email/verify')->icon('mail')
    ->completedUsing(fn ($user) => $user->hasVerifiedEmail()); // auto-detected

KinetixOnboarding::step('read-docs', 'Read the quickstart');   // manual (Mark as done)

// Request-dependent CTA URLs (current team, tenant prefixes): pass a Closure —
// steps register in boot(), so a plain string can't reach request state.
KinetixOnboarding::step('invite-team', 'Invite a teammate')
    ->cta('Invite', fn ($user) => route('teams.members', $user->currentTeam));
```

- A step with `completedUsing` is **auto** (completion computed live, never
  stored); without it the step is **manual** (persisted when the user ticks it).
- `OnboardingManager::for($user)` merges declared steps with persisted manual
  completions + live auto-detection → `OnboardingData` (steps, completedCount,
  total, complete, dismissed). `complete($user,$key)` / `dismiss($user)` persist
  to one `kinetix_onboarding` row per user (per team when `onboarding.teams`).
- **Self-service** (no admin ability): `GET {prefix}/onboarding`,
  `POST {prefix}/onboarding/complete`, `POST {prefix}/onboarding/dismiss`.

```vue
<KinetixOnboardingChecklist />                      <!-- card: progress bar, CTAs, mark-done, dismiss -->
<KinetixOnboardingChecklist variant="sidebar" />    <!-- condensed block for a shadcn <SidebarFooter> -->
```

`useKinetixOnboarding()` → `{ state, load, complete, dismiss }`. The card hides
when dismissed and (default) when complete; `:hide-when-complete="false"` keeps it.

**Payload, not fetch.** With `onboarding.share` on (default) the state rides on
every Inertia response as **`kinetix_onboarding`**, so mounting the checklist
costs no request; `load()` is a no-op then (`load(true)` forces a refetch), and
`complete`/`dismiss` results win over the payload until the next response. The
cost is one progress-row read + every `completedUsing` callback **per response**
— keep them cheap. `for()` is a pure read: no row is written until the user
completes a manual step or dismisses.

**`variant`** (`card` default | `sidebar`) — same component, same state, same
endpoints; only the presentation differs. The `sidebar` variant drops step
descriptions, shows a terse `1 of 3` counter + hairline bar, makes the leading
circle the mark-done control and the row itself the CTA link, and carries
shadcn's `group-data-[collapsible=icon]:hidden` so an icon-collapsed rail hides
it (harmless outside a sidebar). Mount it in the starter kit's `AppSidebar.vue`
above the user menu; keep the checklist to ~6 steps at that width.

---

## Empty states (frontend-only)

```vue
<KinetixEmptyState icon="user" :title="…" :description="…">
  <button>Invite</button>   <!-- default slot = CTAs -->
</KinetixEmptyState>
```

Pure presentational; `icon` is any Kinetix icon name.

---

## Product tour (frontend-only)

```vue
<button data-tour="create">New</button>
<KinetixTour id="records-intro" :steps="[{ target: '[data-tour=create]', title: '…' }]" />
```

- Dependency-free; spotlights targets by CSS selector + positioned tooltip
  (next/back/skip). **Auto-starts once per `id`** (localStorage). `:auto="false"`
  + exposed `start()`/`reset()` for a manual "show me around"/replay.
- `useKinetixTour(id, steps)` → `{ active, current, index, isFirst, isLast,
  start, startOnce, next, prev, finish, skip, reset, hasSeen }`.

### Backend-driven tours (`KinetixTours::tour()` + `<KinetixTours />`)

```php
KinetixTours::tour('employees')
    ->page('Employees/Index')
    ->version(2)                                   // re-show after a content change; id stays
    ->steps([
        TourStep::action('create')                 // [data-action="create"]
            ->titleKey('tours.employees.create')   // translated per request, viewer's locale
            ->descriptionKey('tours.employees.create_body'),
        TourStep::field('salary'),                 // opens the (nested) tab / wizard step holding it
        TourStep::tab('employment'),               // opens the tab
        TourStep::wizardStep('billing'),           // the step in the indicator
        TourStep::tableHeader(),
        TourStep::action('export')->optional(),    // skipped when not on the page
    ]);
```

- **Never** `->title(__('…'))` in a provider's `boot()`: it translates before the
  locale middleware runs (app language, not the user's). Use `titleKey()` /
  `descriptionKey()`.
- Use the step helpers (stable `data-action` / `data-kinetix-field` /
  `data-kinetix-tab` / `data-kinetix-wizard-step` / `data-kinetix-table-header`
  hooks), never shadcn internals (`[data-slot=…]`) or element ids. A wizard
  never opens a step past one with required fields empty — the tour then
  highlights the step's indicator.
- `<KinetixTourTrigger />` replays the current page's tour (renders nothing
  without one); slot `{ start, label, tour }` for menu items.
- `kinetix.tours.skip_when_automated` (default true): no auto-start under
  `navigator.webdriver`; tests of the tour opt in with `?kinetix-tours=1` or
  `localStorage['kinetix.tours.automated'] = '1'`.

i18n `onboarding_*` / `tour_*` (en/es/fr/pt). Tests: `OnboardingTest`,
`useKinetixOnboarding.spec.ts`, `useKinetixTour.spec.ts`.

## UUID / ULID Host Models

This feature's migration builds `user_id` and `team_id` with
`Happones\Kinetix\Support\HostKeys`, which types each column after YOUR model
at migrate time from its primary key (`HasUlids` -> ulid, `HasUuids` -> uuid, string `$keyType` ->
string, else bigint). Pin `kinetix.key_types.user|team` when detection cannot
see the setup; morph ids follow `kinetix.key_types.morph` (default bigint) —
set it when the referenced models use UUIDs/ULIDs. Apps migrated on an older
Kinetix have bigint columns on disk and need their own ALTER migration. Full
recipe: the `kinetix-boost` skill, section "UUID / ULID Host Models".

Kinetix Onboarding Development

When to Apply

Activate this skill when:

  • Declaring checklist steps (KinetixOnboarding::step) or configuring kinetix.onboarding.
  • Mounting <KinetixOnboardingChecklist> / <KinetixEmptyState> / <KinetixTour>.
  • Using useKinetixOnboarding or useKinetixTour.

Documentation

For full details, reference docs/onboarding.md (published at https://happones.github.io/kinetix/onboarding).

Configuration

php artisan vendor:publish --tag=kinetix-onboarding-migrations
php artisan migrate
'onboarding' => [
    'enabled' => env('KINETIX_ONBOARDING_ENABLED', false),
    'share'   => env('KINETIX_ONBOARDING_SHARE', true),  // state on every Inertia response
    'teams'   => env('KINETIX_ONBOARDING_TEAMS', false), // per-team vs per-user progress
],

Checklist (backend-driven)

use Happones\Kinetix\Onboarding\KinetixOnboarding;

KinetixOnboarding::step('verify-email', 'Verify your email')
    ->description('…')->cta('Resend', '/email/verify')->icon('mail')
    ->completedUsing(fn ($user) => $user->hasVerifiedEmail()); // auto-detected

KinetixOnboarding::step('read-docs', 'Read the quickstart');   // manual (Mark as done)

// Request-dependent CTA URLs (current team, tenant prefixes): pass a Closure —
// steps register in boot(), so a plain string can't reach request state.
KinetixOnboarding::step('invite-team', 'Invite a teammate')
    ->cta('Invite', fn ($user) => route('teams.members', $user->currentTeam));
  • A step with completedUsing is auto (completion computed live, never stored); without it the step is manual (persisted when the user ticks it).
  • OnboardingManager::for($user) merges declared steps with persisted manual completions + live auto-detection → OnboardingData (steps, completedCount, total, complete, dismissed). complete($user,$key) / dismiss($user) persist to one kinetix_onboarding row per user (per team when onboarding.teams).
  • Self-service (no admin ability): GET {prefix}/onboarding, POST {prefix}/onboarding/complete, POST {prefix}/onboarding/dismiss.
<KinetixOnboardingChecklist />                      <!-- card: progress bar, CTAs, mark-done, dismiss -->
<KinetixOnboardingChecklist variant="sidebar" />    <!-- condensed block for a shadcn <SidebarFooter> -->

useKinetixOnboarding() → { state, load, complete, dismiss }. The card hides when dismissed and (default) when complete; :hide-when-complete="false" keeps it.

Payload, not fetch. With onboarding.share on (default) the state rides on every Inertia response as kinetix_onboarding, so mounting the checklist costs no request; load() is a no-op then (load(true) forces a refetch), and complete/dismiss results win over the payload until the next response. The cost is one progress-row read + every completedUsing callback per response — keep them cheap. for() is a pure read: no row is written until the user completes a manual step or dismisses.

variant (card default | sidebar) — same component, same state, same endpoints; only the presentation differs. The sidebar variant drops step descriptions, shows a terse 1 of 3 counter + hairline bar, makes the leading circle the mark-done control and the row itself the CTA link, and carries shadcn's group-data-[collapsible=icon]:hidden so an icon-collapsed rail hides it (harmless outside a sidebar). Mount it in the starter kit's AppSidebar.vue above the user menu; keep the checklist to ~6 steps at that width.


Empty states (frontend-only)

<KinetixEmptyState icon="user" :title="…" :description="…">
  <button>Invite</button>   <!-- default slot = CTAs -->
</KinetixEmptyState>

Pure presentational; icon is any Kinetix icon name.


Product tour (frontend-only)

<button data-tour="create">New</button>
<KinetixTour id="records-intro" :steps="[{ target: '[data-tour=create]', title: '…' }]" />
  • Dependency-free; spotlights targets by CSS selector + positioned tooltip (next/back/skip). Auto-starts once per id (localStorage). :auto="false"
    • exposed start()/reset() for a manual "show me around"/replay.
  • useKinetixTour(id, steps) → { active, current, index, isFirst, isLast, start, startOnce, next, prev, finish, skip, reset, hasSeen }.

Backend-driven tours (KinetixTours::tour() + <KinetixTours />)

KinetixTours::tour('employees')
    ->page('Employees/Index')
    ->version(2)                                   // re-show after a content change; id stays
    ->steps([
        TourStep::action('create')                 // [data-action="create"]
            ->titleKey('tours.employees.create')   // translated per request, viewer's locale
            ->descriptionKey('tours.employees.create_body'),
        TourStep::field('salary'),                 // opens the (nested) tab / wizard step holding it
        TourStep::tab('employment'),               // opens the tab
        TourStep::wizardStep('billing'),           // the step in the indicator
        TourStep::tableHeader(),
        TourStep::action('export')->optional(),    // skipped when not on the page
    ]);
  • Never ->title(__('…')) in a provider's boot(): it translates before the locale middleware runs (app language, not the user's). Use titleKey() / descriptionKey().
  • Use the step helpers (stable data-action / data-kinetix-field / data-kinetix-tab / data-kinetix-wizard-step / data-kinetix-table-header hooks), never shadcn internals ([data-slot=…]) or element ids. A wizard never opens a step past one with required fields empty — the tour then highlights the step's indicator.
  • <KinetixTourTrigger /> replays the current page's tour (renders nothing without one); slot { start, label, tour } for menu items.
  • kinetix.tours.skip_when_automated (default true): no auto-start under navigator.webdriver; tests of the tour opt in with ?kinetix-tours=1 or localStorage['kinetix.tours.automated'] = '1'.

i18n onboarding_* / tour_* (en/es/fr/pt). Tests: OnboardingTest, useKinetixOnboarding.spec.ts, useKinetixTour.spec.ts.

UUID / ULID Host Models

This feature's migration builds user_id and team_id with Happones\Kinetix\Support\HostKeys, which types each column after YOUR model at migrate time from its primary key (HasUlids -> ulid, HasUuids -> uuid, string $keyType -> string, else bigint). Pin kinetix.key_types.user|team when detection cannot see the setup; morph ids follow kinetix.key_types.morph (default bigint) — set it when the referenced models use UUIDs/ULIDs. Apps migrated on an older Kinetix have bigint columns on disk and need their own ALTER migration. Full recipe: the kinetix-boost skill, section "UUID / ULID Host Models".

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect