@baselineos/persona 0.1.0

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/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@baselineos/persona",
3
+ "version": "0.1.0",
4
+ "description": "Baseline Protocol - Persona Engine & Adaptive UI System",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "dependencies": {
15
+ "@baselineos/protocol-core": "1.0.0"
16
+ },
17
+ "devDependencies": {
18
+ "tsup": "^8.0.0",
19
+ "typescript": "^5.7.0",
20
+ "vitest": "^2.1.0"
21
+ },
22
+ "license": "Apache-2.0",
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "scripts": {
27
+ "build": "tsup src/index.ts --format esm --dts",
28
+ "dev": "tsup src/index.ts --format esm --dts --watch",
29
+ "test": "vitest run",
30
+ "lint": "eslint src/",
31
+ "typecheck": "tsc --noEmit",
32
+ "clean": "rm -rf dist"
33
+ }
34
+ }
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Tests for PersonaUISystem
3
+ * Sprint 49
4
+ */
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import { PersonaUISystem } from '../ui.js';
7
+ import { BaselinePersonaEngine } from '../engine.js';
8
+ import type { WorkflowInstance, UIComponent } from '../ui.js';
9
+
10
+ describe('PersonaUISystem', () => {
11
+ let engine: BaselinePersonaEngine;
12
+ let ui: PersonaUISystem;
13
+
14
+ beforeEach(() => {
15
+ engine = new BaselinePersonaEngine();
16
+ ui = new PersonaUISystem(engine);
17
+ });
18
+
19
+ afterEach(() => {
20
+ ui.dispose();
21
+ });
22
+
23
+ // ─── Initialization ───────────────────────────────────────────────────────
24
+
25
+ it('initializes with default components', () => {
26
+ const status = ui.getStatus();
27
+ expect(status.initialized).toBe(true);
28
+ expect(status.components).toBeGreaterThanOrEqual(5);
29
+ });
30
+
31
+ it('registers default persona-selector component', () => {
32
+ const selector = ui.getComponent('persona-selector');
33
+ expect(selector).toBeDefined();
34
+ expect(selector!.type).toBe('selector');
35
+ expect(selector!.size).toEqual({ width: 320, height: 480 });
36
+ });
37
+
38
+ it('registers default studio-interface component', () => {
39
+ const studio = ui.getComponent('studio-interface');
40
+ expect(studio).toBeDefined();
41
+ expect(studio!.type).toBe('studio');
42
+ });
43
+
44
+ it('registers default workflow-engine component', () => {
45
+ const wf = ui.getComponent('workflow-engine');
46
+ expect(wf).toBeDefined();
47
+ expect(wf!.type).toBe('workflow');
48
+ });
49
+
50
+ // ─── Component Management ─────────────────────────────────────────────────
51
+
52
+ it('registers a custom component', () => {
53
+ const component: UIComponent = {
54
+ id: 'custom-panel',
55
+ type: 'panel',
56
+ visible: true,
57
+ position: { x: 100, y: 100 },
58
+ size: { width: 400, height: 300 },
59
+ config: {},
60
+ state: {},
61
+ };
62
+ ui.registerComponent(component);
63
+ expect(ui.getComponent('custom-panel')).toBeDefined();
64
+ });
65
+
66
+ it('emits component:registered event', () => {
67
+ const events: any[] = [];
68
+ ui.on('component:registered', (e) => events.push(e));
69
+ ui.registerComponent({
70
+ id: 'test',
71
+ type: 'panel',
72
+ visible: true,
73
+ position: { x: 0, y: 0 },
74
+ size: { width: 100, height: 100 },
75
+ config: {},
76
+ state: {},
77
+ });
78
+ expect(events.length).toBe(1);
79
+ expect(events[0].componentId).toBe('test');
80
+ });
81
+
82
+ it('updates component properties', () => {
83
+ ui.updateComponent('persona-selector', {
84
+ position: { x: 50, y: 50 },
85
+ config: { showSearch: false },
86
+ });
87
+ const updated = ui.getComponent('persona-selector');
88
+ expect(updated!.position).toEqual({ x: 50, y: 50 });
89
+ expect(updated!.config.showSearch).toBe(false);
90
+ });
91
+
92
+ it('shows and hides components', () => {
93
+ ui.hideComponent('persona-selector');
94
+ expect(ui.getComponent('persona-selector')!.visible).toBe(false);
95
+ ui.showComponent('persona-selector');
96
+ expect(ui.getComponent('persona-selector')!.visible).toBe(true);
97
+ });
98
+
99
+ it('removes a component', () => {
100
+ expect(ui.removeComponent('persona-selector')).toBe(true);
101
+ expect(ui.getComponent('persona-selector')).toBeUndefined();
102
+ });
103
+
104
+ it('lists all and visible components', () => {
105
+ const all = ui.getAllComponents();
106
+ expect(all.length).toBeGreaterThanOrEqual(5);
107
+ const visible = ui.getVisibleComponents();
108
+ expect(visible.length).toBeLessThanOrEqual(all.length);
109
+ });
110
+
111
+ // ─── Persona Selector ─────────────────────────────────────────────────────
112
+
113
+ it('opens and closes selector', () => {
114
+ ui.openSelector();
115
+ expect(ui.getSelectorState().isOpen).toBe(true);
116
+ ui.closeSelector();
117
+ expect(ui.getSelectorState().isOpen).toBe(false);
118
+ });
119
+
120
+ it('toggles selector', () => {
121
+ ui.toggleSelector();
122
+ expect(ui.getSelectorState().isOpen).toBe(true);
123
+ ui.toggleSelector();
124
+ expect(ui.getSelectorState().isOpen).toBe(false);
125
+ });
126
+
127
+ it('returns persona options', () => {
128
+ const options = ui.getPersonaOptions();
129
+ expect(options.length).toBe(5);
130
+ expect(options.map((o) => o.id)).toContain('designer');
131
+ expect(options.map((o) => o.id)).toContain('developer');
132
+ });
133
+
134
+ it('filters persona options by search query', () => {
135
+ const filtered = ui.setSearchQuery('design');
136
+ expect(filtered.length).toBeGreaterThan(0);
137
+ expect(filtered.some((o) => o.name.toLowerCase().includes('design'))).toBe(true);
138
+ });
139
+
140
+ it('returns all options for empty search', () => {
141
+ const filtered = ui.setSearchQuery('');
142
+ expect(filtered.length).toBe(5);
143
+ });
144
+
145
+ it('hovers persona updates state', () => {
146
+ ui.hoverPersona('developer');
147
+ expect(ui.getSelectorState().hoveredPersonaId).toBe('developer');
148
+ });
149
+
150
+ it('selects persona updates state and closes selector', () => {
151
+ ui.openSelector();
152
+ ui.selectPersona('developer', 'user-1');
153
+ const state = ui.getSelectorState();
154
+ expect(state.selectedPersonaId).toBe('developer');
155
+ expect(state.isOpen).toBe(false);
156
+ });
157
+
158
+ // ─── Workflow Engine ──────────────────────────────────────────────────────
159
+
160
+ it('starts a workflow', () => {
161
+ const wf = ui.startWorkflow('development', 'developer', 'user-1');
162
+ expect(wf.id).toBeDefined();
163
+ expect(wf.type).toBe('development');
164
+ expect(wf.personaId).toBe('developer');
165
+ expect(wf.steps.length).toBeGreaterThan(0);
166
+ });
167
+
168
+ it('workflow has correct initial state', () => {
169
+ const wf = ui.startWorkflow('design', 'designer', 'user-1');
170
+ expect(wf.steps[0].status).toBe('active');
171
+ expect(wf.steps.slice(1).every((s) => s.status === 'pending')).toBe(true);
172
+ });
173
+
174
+ it('retrieves workflow by id', () => {
175
+ const wf = ui.startWorkflow('analysis', 'data_scientist', 'user-1');
176
+ expect(ui.getWorkflow(wf.id)).toBeDefined();
177
+ expect(ui.getWorkflow(wf.id)?.type).toBe('analysis');
178
+ });
179
+
180
+ it('lists active and completed workflows', () => {
181
+ ui.startWorkflow('development', 'developer', 'user-1');
182
+ const active = ui.getActiveWorkflows();
183
+ expect(active.length).toBeGreaterThanOrEqual(0); // may have completed by now
184
+ const all = ui.getAllWorkflows();
185
+ expect(all.length).toBe(1);
186
+ });
187
+
188
+ it('enforces max concurrent workflows', () => {
189
+ const smallUi = new PersonaUISystem(engine, { maxWorkflows: 2 });
190
+ smallUi.startWorkflow('design', 'designer', 'user-1');
191
+ smallUi.startWorkflow('analysis', 'data_scientist', 'user-1');
192
+ expect(() => smallUi.startWorkflow('development', 'developer', 'user-1')).toThrow('Maximum concurrent workflows');
193
+ smallUi.dispose();
194
+ });
195
+
196
+ it('cancels a running workflow', async () => {
197
+ const wf = ui.startWorkflow('design', 'designer', 'user-1');
198
+ // Cancel immediately — may or may not succeed depending on timing
199
+ const cancelled = ui.cancelWorkflow(wf.id);
200
+ // Either it was still running and got cancelled, or it already finished
201
+ if (cancelled) {
202
+ expect(ui.getWorkflow(wf.id)?.status).toBe('cancelled');
203
+ }
204
+ });
205
+
206
+ it('cancel returns false for completed workflow', async () => {
207
+ const wf = ui.startWorkflow('default', 'developer', 'user-1');
208
+ // Wait for completion
209
+ await new Promise((r) => setTimeout(r, 2000));
210
+ expect(ui.cancelWorkflow(wf.id)).toBe(false);
211
+ });
212
+
213
+ it('emits workflow:started event', () => {
214
+ const events: any[] = [];
215
+ ui.on('workflow:started', (e) => events.push(e));
216
+ ui.startWorkflow('design', 'designer', 'user-1');
217
+ expect(events.length).toBe(1);
218
+ expect(events[0].type).toBe('design');
219
+ });
220
+
221
+ // ─── Progress Tracking ────────────────────────────────────────────────────
222
+
223
+ it('tracks progress for started workflow', () => {
224
+ const wf = ui.startWorkflow('development', 'developer', 'user-1');
225
+ const progress = ui.getProgress(wf.id);
226
+ expect(progress).toBeDefined();
227
+ expect(progress!.workflowId).toBe(wf.id);
228
+ expect(progress!.totalSteps).toBeGreaterThan(0);
229
+ });
230
+
231
+ it('returns undefined for nonexistent workflow progress', () => {
232
+ expect(ui.getProgress('nope')).toBeUndefined();
233
+ });
234
+
235
+ // ─── Result Display ───────────────────────────────────────────────────────
236
+
237
+ it('creates a result display', () => {
238
+ const result = ui.createResultDisplay('wf-1', 'chart', 'Revenue Analysis', { value: 42 });
239
+ expect(result.id).toBeDefined();
240
+ expect(result.workflowId).toBe('wf-1');
241
+ expect(result.title).toBe('Revenue Analysis');
242
+ });
243
+
244
+ it('retrieves and removes result display', () => {
245
+ const result = ui.createResultDisplay('wf-1', 'table', 'Data', {});
246
+ expect(ui.getResultDisplay(result.id)).toBeDefined();
247
+ expect(ui.removeResultDisplay(result.id)).toBe(true);
248
+ expect(ui.getResultDisplay(result.id)).toBeUndefined();
249
+ });
250
+
251
+ it('gets results for workflow', () => {
252
+ ui.createResultDisplay('wf-1', 'table', 'A', {});
253
+ ui.createResultDisplay('wf-1', 'chart', 'B', {});
254
+ ui.createResultDisplay('wf-2', 'table', 'C', {});
255
+ expect(ui.getResultsForWorkflow('wf-1').length).toBe(2);
256
+ });
257
+
258
+ it('clears all results', () => {
259
+ ui.createResultDisplay('wf-1', 'table', 'A', {});
260
+ ui.createResultDisplay('wf-2', 'chart', 'B', {});
261
+ ui.clearResults();
262
+ expect(ui.getAllResults().length).toBe(0);
263
+ });
264
+
265
+ // ─── Layout & Theme ───────────────────────────────────────────────────────
266
+
267
+ it('sets and gets layout', () => {
268
+ ui.setLayout('grid');
269
+ expect(ui.getLayout()).toBe('grid');
270
+ });
271
+
272
+ it('sets and gets theme', () => {
273
+ ui.setTheme('dark');
274
+ expect(ui.getTheme()).toBe('dark');
275
+ });
276
+
277
+ it('emits layout:changed and theme:changed events', () => {
278
+ const events: string[] = [];
279
+ ui.on('layout:changed', () => events.push('layout'));
280
+ ui.on('theme:changed', () => events.push('theme'));
281
+ ui.setLayout('sidebar');
282
+ ui.setTheme('light');
283
+ expect(events).toEqual(['layout', 'theme']);
284
+ });
285
+
286
+ // ─── Frame Management ─────────────────────────────────────────────────────
287
+
288
+ it('activates and deactivates frames', () => {
289
+ ui.activateFrame('code-editor');
290
+ ui.activateFrame('terminal');
291
+ expect(ui.getActiveFrames()).toContain('code-editor');
292
+ expect(ui.getActiveFrames()).toContain('terminal');
293
+
294
+ ui.deactivateFrame('code-editor');
295
+ expect(ui.getActiveFrames()).not.toContain('code-editor');
296
+ });
297
+
298
+ it('does not duplicate frames on re-activate', () => {
299
+ ui.activateFrame('editor');
300
+ ui.activateFrame('editor');
301
+ expect(ui.getActiveFrames().filter((f) => f === 'editor').length).toBe(1);
302
+ });
303
+
304
+ it('sets all active frames', () => {
305
+ ui.setActiveFrames(['a', 'b', 'c']);
306
+ expect(ui.getActiveFrames()).toEqual(['a', 'b', 'c']);
307
+ });
308
+
309
+ // ─── Status & Analytics ───────────────────────────────────────────────────
310
+
311
+ it('reports status', () => {
312
+ const status = ui.getStatus();
313
+ expect(status.initialized).toBe(true);
314
+ expect(status.components).toBeGreaterThan(0);
315
+ expect(status.selectorState).toBeDefined();
316
+ expect(status.studioState).toBeDefined();
317
+ });
318
+
319
+ it('reports UI analytics', () => {
320
+ ui.startWorkflow('design', 'designer', 'user-1');
321
+ const analytics = ui.getUIAnalytics();
322
+ expect((analytics as any).totalWorkflows).toBe(1);
323
+ expect((analytics as any).workflowsByType).toHaveProperty('design');
324
+ });
325
+
326
+ // ─── Cleanup ──────────────────────────────────────────────────────────────
327
+
328
+ it('dispose clears all state', () => {
329
+ ui.startWorkflow('design', 'designer', 'user-1');
330
+ ui.createResultDisplay('wf-1', 'table', 'A', {});
331
+ ui.dispose();
332
+ expect(ui.getAllComponents().length).toBe(0);
333
+ expect(ui.getAllWorkflows().length).toBe(0);
334
+ expect(ui.getAllResults().length).toBe(0);
335
+ });
336
+ });
@@ -0,0 +1,16 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { BaselinePersonaEngine } from '../index.js';
3
+
4
+ describe('persona', () => {
5
+ it('should instantiate BaselinePersonaEngine', () => {
6
+ const persona = new BaselinePersonaEngine();
7
+ expect(persona).toBeDefined();
8
+ });
9
+
10
+ it('should load 5 core personas', () => {
11
+ const persona = new BaselinePersonaEngine();
12
+ const status = persona.getStatus();
13
+ expect(status.initialized).toBe(true);
14
+ expect(status.totalPersonas).toBe(5);
15
+ });
16
+ });