@holoscript/plugin-fitness-wellness 2.0.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.
@@ -0,0 +1,26 @@
1
+ /** @rep_counter Trait — Repetition counting and form tracking. @trait rep_counter */
2
+ import type { TraitHandler, HSPlusNode, TraitContext, TraitEvent } from './types';
3
+
4
+ export interface RepCounterConfig { targetReps: number; exerciseName: string; formCheckEnabled: boolean; tempoUp: number; tempoDown: number; }
5
+ export interface RepCounterState { currentReps: number; goodFormReps: number; badFormReps: number; isTracking: boolean; }
6
+
7
+ const defaultConfig: RepCounterConfig = { targetReps: 10, exerciseName: '', formCheckEnabled: true, tempoUp: 2, tempoDown: 3 };
8
+
9
+ export function createRepCounterHandler(): TraitHandler<RepCounterConfig> {
10
+ return { name: 'rep_counter', defaultConfig,
11
+ onAttach(n: HSPlusNode, _c: RepCounterConfig, ctx: TraitContext) { n.__repState = { currentReps: 0, goodFormReps: 0, badFormReps: 0, isTracking: false }; ctx.emit?.('rep:ready'); },
12
+ onDetach(n: HSPlusNode, _c: RepCounterConfig, ctx: TraitContext) { delete n.__repState; ctx.emit?.('rep:stopped'); },
13
+ onUpdate() {},
14
+ onEvent(n: HSPlusNode, c: RepCounterConfig, ctx: TraitContext, e: TraitEvent) {
15
+ const s = n.__repState as RepCounterState | undefined; if (!s) return;
16
+ if (e.type === 'rep:start') { s.isTracking = true; s.currentReps = 0; s.goodFormReps = 0; s.badFormReps = 0; ctx.emit?.('rep:tracking'); }
17
+ if (e.type === 'rep:count' && s.isTracking) {
18
+ s.currentReps++;
19
+ const goodForm = (e.payload?.goodForm as boolean) ?? true;
20
+ if (goodForm) s.goodFormReps++; else s.badFormReps++;
21
+ ctx.emit?.('rep:counted', { reps: s.currentReps, target: c.targetReps, formRate: s.goodFormReps / s.currentReps });
22
+ if (s.currentReps >= c.targetReps) { s.isTracking = false; ctx.emit?.('rep:target_reached', { total: s.currentReps, goodForm: s.goodFormReps }); }
23
+ }
24
+ },
25
+ };
26
+ }
@@ -0,0 +1,23 @@
1
+ /** @workout Trait — Workout session definition. @trait workout */
2
+ import type { TraitHandler, HSPlusNode, TraitContext, TraitEvent } from './types';
3
+
4
+ export type WorkoutType = 'strength' | 'cardio' | 'hiit' | 'yoga' | 'pilates' | 'crossfit' | 'swimming' | 'cycling' | 'running';
5
+ export interface ExerciseSet { exerciseId: string; reps: number; weightKg?: number; durationS?: number; restS: number; }
6
+ export interface WorkoutConfig { name: string; type: WorkoutType; exercises: ExerciseSet[]; estimatedDurationMin: number; difficulty: 'beginner' | 'intermediate' | 'advanced'; calorieTarget?: number; }
7
+ export interface WorkoutState { isActive: boolean; currentExercise: number; elapsedS: number; caloriesBurned: number; completedSets: number; }
8
+
9
+ const defaultConfig: WorkoutConfig = { name: '', type: 'strength', exercises: [], estimatedDurationMin: 30, difficulty: 'intermediate' };
10
+
11
+ export function createWorkoutHandler(): TraitHandler<WorkoutConfig> {
12
+ return { name: 'workout', defaultConfig,
13
+ onAttach(n: HSPlusNode, _c: WorkoutConfig, ctx: TraitContext) { n.__workoutState = { isActive: false, currentExercise: 0, elapsedS: 0, caloriesBurned: 0, completedSets: 0 }; ctx.emit?.('workout:ready'); },
14
+ onDetach(n: HSPlusNode, _c: WorkoutConfig, ctx: TraitContext) { delete n.__workoutState; ctx.emit?.('workout:ended'); },
15
+ onUpdate(n: HSPlusNode, _c: WorkoutConfig, _ctx: TraitContext, delta: number) { const s = n.__workoutState as WorkoutState | undefined; if (s?.isActive) { s.elapsedS += delta / 1000; s.caloriesBurned += (delta / 1000) * 0.15; } },
16
+ onEvent(n: HSPlusNode, c: WorkoutConfig, ctx: TraitContext, e: TraitEvent) {
17
+ const s = n.__workoutState as WorkoutState | undefined; if (!s) return;
18
+ if (e.type === 'workout:start') { s.isActive = true; ctx.emit?.('workout:started', { type: c.type }); }
19
+ if (e.type === 'workout:complete_set') { s.completedSets++; if (s.completedSets >= c.exercises.length) { s.isActive = false; ctx.emit?.('workout:completed', { calories: Math.round(s.caloriesBurned), duration: Math.round(s.elapsedS) }); } else { s.currentExercise++; ctx.emit?.('workout:next_exercise', { index: s.currentExercise }); } }
20
+ if (e.type === 'workout:pause') { s.isActive = false; ctx.emit?.('workout:paused'); }
21
+ },
22
+ };
23
+ }
@@ -0,0 +1,4 @@
1
+ export interface HSPlusNode { id?: string; properties?: Record<string, unknown>; [key: string]: unknown; }
2
+ export interface TraitContext { emit?: (event: string, payload?: unknown) => void; [key: string]: unknown; }
3
+ export interface TraitEvent { type: string; payload?: Record<string, unknown>; [key: string]: unknown; }
4
+ export interface TraitHandler<T = unknown> { name: string; defaultConfig: T; onAttach(n: HSPlusNode, c: T, ctx: TraitContext): void; onDetach(n: HSPlusNode, c: T, ctx: TraitContext): void; onUpdate(n: HSPlusNode, c: T, ctx: TraitContext, d: number): void; onEvent(n: HSPlusNode, c: T, ctx: TraitContext, e: TraitEvent): void; }
package/tsconfig.json ADDED
@@ -0,0 +1 @@
1
+ { "extends": "../../../tsconfig.json", "compilerOptions": { "outDir": "dist", "rootDir": "src", "declaration": true }, "include": ["src"] }
@@ -0,0 +1,22 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ import { resolve } from 'path';
3
+
4
+ // resolve.alias is REQUIRED: without it, `@holoscript/core/runtime` resolves to
5
+ // the STALE built dist and registerPluginTraits is missing ("not a function").
6
+ // Point the subpath imports at core/engine source so the runtime barrel
7
+ // (core/src/runtime.ts) and shared registrar resolve from source. Mirrors
8
+ // government-civic-plugin/vitest.config.ts.
9
+ export default defineConfig({
10
+ resolve: {
11
+ alias: {
12
+ '@holoscript/engine': resolve(__dirname, '../../engine/src'),
13
+ '@holoscript/core': resolve(__dirname, '../../core/src'),
14
+ },
15
+ },
16
+ test: {
17
+ globals: true,
18
+ environment: 'node',
19
+ include: ['src/**/*.test.ts'],
20
+ passWithNoTests: true,
21
+ },
22
+ });