reviewflow 3.28.0 → 3.30.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.
Files changed (139) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/dashboard/index.html +74 -9
  3. package/dist/dashboard/modules/emberAvatar.d.ts +134 -0
  4. package/dist/dashboard/modules/emberAvatar.d.ts.map +1 -0
  5. package/dist/dashboard/modules/emberAvatar.js +192 -0
  6. package/dist/dashboard/modules/emberAvatar.js.map +1 -0
  7. package/dist/dashboard/modules/emberAvatarRenderer.d.ts +42 -0
  8. package/dist/dashboard/modules/emberAvatarRenderer.d.ts.map +1 -0
  9. package/dist/dashboard/modules/emberAvatarRenderer.js +119 -0
  10. package/dist/dashboard/modules/emberAvatarRenderer.js.map +1 -0
  11. package/dist/dashboard/modules/emberChat.d.ts +77 -0
  12. package/dist/dashboard/modules/emberChat.d.ts.map +1 -0
  13. package/dist/dashboard/modules/emberChat.js +175 -0
  14. package/dist/dashboard/modules/emberChat.js.map +1 -0
  15. package/dist/dashboard/styles.css +126 -0
  16. package/dist/main/routes.d.ts.map +1 -1
  17. package/dist/main/routes.js +32 -0
  18. package/dist/main/routes.js.map +1 -1
  19. package/dist/modules/ember-chat/entities/emberMessage/emberMessage.guard.d.ts +4 -0
  20. package/dist/modules/ember-chat/entities/emberMessage/emberMessage.guard.d.ts.map +1 -0
  21. package/dist/modules/ember-chat/entities/emberMessage/emberMessage.guard.js +4 -0
  22. package/dist/modules/ember-chat/entities/emberMessage/emberMessage.guard.js.map +1 -0
  23. package/dist/modules/ember-chat/entities/emberMessage/emberMessage.schema.d.ts +6 -0
  24. package/dist/modules/ember-chat/entities/emberMessage/emberMessage.schema.d.ts.map +1 -0
  25. package/dist/modules/ember-chat/entities/emberMessage/emberMessage.schema.js +5 -0
  26. package/dist/modules/ember-chat/entities/emberMessage/emberMessage.schema.js.map +1 -0
  27. package/dist/modules/ember-chat/entities/emberSession/emberSession.schema.d.ts +7 -0
  28. package/dist/modules/ember-chat/entities/emberSession/emberSession.schema.d.ts.map +1 -0
  29. package/dist/modules/ember-chat/entities/emberSession/emberSession.schema.js +3 -0
  30. package/dist/modules/ember-chat/entities/emberSession/emberSession.schema.js.map +1 -0
  31. package/dist/modules/ember-chat/entities/emberSession/emberSessionState.d.ts +13 -0
  32. package/dist/modules/ember-chat/entities/emberSession/emberSessionState.d.ts.map +1 -0
  33. package/dist/modules/ember-chat/entities/emberSession/emberSessionState.js +35 -0
  34. package/dist/modules/ember-chat/entities/emberSession/emberSessionState.js.map +1 -0
  35. package/dist/modules/ember-chat/entities/emberSession/emberSessionTransport.gateway.d.ts +26 -0
  36. package/dist/modules/ember-chat/entities/emberSession/emberSessionTransport.gateway.d.ts.map +1 -0
  37. package/dist/modules/ember-chat/entities/emberSession/emberSessionTransport.gateway.js +2 -0
  38. package/dist/modules/ember-chat/entities/emberSession/emberSessionTransport.gateway.js.map +1 -0
  39. package/dist/modules/ember-chat/entities/emberTool/emberTool.gateway.d.ts +11 -0
  40. package/dist/modules/ember-chat/entities/emberTool/emberTool.gateway.d.ts.map +1 -0
  41. package/dist/modules/ember-chat/entities/emberTool/emberTool.gateway.js +2 -0
  42. package/dist/modules/ember-chat/entities/emberTool/emberTool.gateway.js.map +1 -0
  43. package/dist/modules/ember-chat/interface-adapters/controllers/http/emberChat.routes.d.ts +15 -0
  44. package/dist/modules/ember-chat/interface-adapters/controllers/http/emberChat.routes.d.ts.map +1 -0
  45. package/dist/modules/ember-chat/interface-adapters/controllers/http/emberChat.routes.js +61 -0
  46. package/dist/modules/ember-chat/interface-adapters/controllers/http/emberChat.routes.js.map +1 -0
  47. package/dist/modules/ember-chat/interface-adapters/gateways/emberReadData.composite.gateway.d.ts +24 -0
  48. package/dist/modules/ember-chat/interface-adapters/gateways/emberReadData.composite.gateway.d.ts.map +1 -0
  49. package/dist/modules/ember-chat/interface-adapters/gateways/emberReadData.composite.gateway.js +19 -0
  50. package/dist/modules/ember-chat/interface-adapters/gateways/emberReadData.composite.gateway.js.map +1 -0
  51. package/dist/modules/ember-chat/interface-adapters/gateways/emberSessionTransport.claude.gateway.d.ts +13 -0
  52. package/dist/modules/ember-chat/interface-adapters/gateways/emberSessionTransport.claude.gateway.d.ts.map +1 -0
  53. package/dist/modules/ember-chat/interface-adapters/gateways/emberSessionTransport.claude.gateway.js +130 -0
  54. package/dist/modules/ember-chat/interface-adapters/gateways/emberSessionTransport.claude.gateway.js.map +1 -0
  55. package/dist/modules/ember-chat/interface-adapters/gateways/emberStreamJson.parser.d.ts +18 -0
  56. package/dist/modules/ember-chat/interface-adapters/gateways/emberStreamJson.parser.d.ts.map +1 -0
  57. package/dist/modules/ember-chat/interface-adapters/gateways/emberStreamJson.parser.js +33 -0
  58. package/dist/modules/ember-chat/interface-adapters/gateways/emberStreamJson.parser.js.map +1 -0
  59. package/dist/modules/ember-chat/interface-adapters/presenters/emberStatus.presenter.d.ts +20 -0
  60. package/dist/modules/ember-chat/interface-adapters/presenters/emberStatus.presenter.d.ts.map +1 -0
  61. package/dist/modules/ember-chat/interface-adapters/presenters/emberStatus.presenter.js +28 -0
  62. package/dist/modules/ember-chat/interface-adapters/presenters/emberStatus.presenter.js.map +1 -0
  63. package/dist/modules/ember-chat/services/emberSystemPrompt.d.ts +12 -0
  64. package/dist/modules/ember-chat/services/emberSystemPrompt.d.ts.map +1 -0
  65. package/dist/modules/ember-chat/services/emberSystemPrompt.js +29 -0
  66. package/dist/modules/ember-chat/services/emberSystemPrompt.js.map +1 -0
  67. package/dist/modules/ember-chat/usecases/askEmber/askEmber.usecase.d.ts +23 -0
  68. package/dist/modules/ember-chat/usecases/askEmber/askEmber.usecase.d.ts.map +1 -0
  69. package/dist/modules/ember-chat/usecases/askEmber/askEmber.usecase.js +23 -0
  70. package/dist/modules/ember-chat/usecases/askEmber/askEmber.usecase.js.map +1 -0
  71. package/dist/modules/ember-chat/usecases/emberSession/emberSessionRegistry.d.ts +36 -0
  72. package/dist/modules/ember-chat/usecases/emberSession/emberSessionRegistry.d.ts.map +1 -0
  73. package/dist/modules/ember-chat/usecases/emberSession/emberSessionRegistry.js +59 -0
  74. package/dist/modules/ember-chat/usecases/emberSession/emberSessionRegistry.js.map +1 -0
  75. package/dist/tests/acceptance/189-ember-readonly-review-chat.acceptance.test.d.ts +2 -0
  76. package/dist/tests/acceptance/189-ember-readonly-review-chat.acceptance.test.d.ts.map +1 -0
  77. package/dist/tests/acceptance/189-ember-readonly-review-chat.acceptance.test.js +124 -0
  78. package/dist/tests/acceptance/189-ember-readonly-review-chat.acceptance.test.js.map +1 -0
  79. package/dist/tests/factories/emberMessage.factory.d.ts +5 -0
  80. package/dist/tests/factories/emberMessage.factory.d.ts.map +1 -0
  81. package/dist/tests/factories/emberMessage.factory.js +9 -0
  82. package/dist/tests/factories/emberMessage.factory.js.map +1 -0
  83. package/dist/tests/stubs/emberReadData.stub.d.ts +20 -0
  84. package/dist/tests/stubs/emberReadData.stub.d.ts.map +1 -0
  85. package/dist/tests/stubs/emberReadData.stub.js +31 -0
  86. package/dist/tests/stubs/emberReadData.stub.js.map +1 -0
  87. package/dist/tests/stubs/emberSessionTransport.stub.d.ts +18 -0
  88. package/dist/tests/stubs/emberSessionTransport.stub.d.ts.map +1 -0
  89. package/dist/tests/stubs/emberSessionTransport.stub.js +75 -0
  90. package/dist/tests/stubs/emberSessionTransport.stub.js.map +1 -0
  91. package/dist/tests/units/dashboard/modules/emberAvatar.test.d.ts +2 -0
  92. package/dist/tests/units/dashboard/modules/emberAvatar.test.d.ts.map +1 -0
  93. package/dist/tests/units/dashboard/modules/emberAvatar.test.js +74 -0
  94. package/dist/tests/units/dashboard/modules/emberAvatar.test.js.map +1 -0
  95. package/dist/tests/units/dashboard/modules/emberAvatarRenderer.test.d.ts +2 -0
  96. package/dist/tests/units/dashboard/modules/emberAvatarRenderer.test.d.ts.map +1 -0
  97. package/dist/tests/units/dashboard/modules/emberAvatarRenderer.test.js +44 -0
  98. package/dist/tests/units/dashboard/modules/emberAvatarRenderer.test.js.map +1 -0
  99. package/dist/tests/units/dashboard/modules/emberChat.test.d.ts +2 -0
  100. package/dist/tests/units/dashboard/modules/emberChat.test.d.ts.map +1 -0
  101. package/dist/tests/units/dashboard/modules/emberChat.test.js +59 -0
  102. package/dist/tests/units/dashboard/modules/emberChat.test.js.map +1 -0
  103. package/dist/tests/units/modules/ember-chat/controllers/emberChat.routes.test.d.ts +2 -0
  104. package/dist/tests/units/modules/ember-chat/controllers/emberChat.routes.test.d.ts.map +1 -0
  105. package/dist/tests/units/modules/ember-chat/controllers/emberChat.routes.test.js +101 -0
  106. package/dist/tests/units/modules/ember-chat/controllers/emberChat.routes.test.js.map +1 -0
  107. package/dist/tests/units/modules/ember-chat/entities/emberMessage.guard.test.d.ts +2 -0
  108. package/dist/tests/units/modules/ember-chat/entities/emberMessage.guard.test.d.ts.map +1 -0
  109. package/dist/tests/units/modules/ember-chat/entities/emberMessage.guard.test.js +24 -0
  110. package/dist/tests/units/modules/ember-chat/entities/emberMessage.guard.test.js.map +1 -0
  111. package/dist/tests/units/modules/ember-chat/entities/emberSessionState.test.d.ts +2 -0
  112. package/dist/tests/units/modules/ember-chat/entities/emberSessionState.test.d.ts.map +1 -0
  113. package/dist/tests/units/modules/ember-chat/entities/emberSessionState.test.js +42 -0
  114. package/dist/tests/units/modules/ember-chat/entities/emberSessionState.test.js.map +1 -0
  115. package/dist/tests/units/modules/ember-chat/gateways/emberReadData.composite.gateway.test.d.ts +2 -0
  116. package/dist/tests/units/modules/ember-chat/gateways/emberReadData.composite.gateway.test.d.ts.map +1 -0
  117. package/dist/tests/units/modules/ember-chat/gateways/emberReadData.composite.gateway.test.js +75 -0
  118. package/dist/tests/units/modules/ember-chat/gateways/emberReadData.composite.gateway.test.js.map +1 -0
  119. package/dist/tests/units/modules/ember-chat/gateways/emberStreamJson.parser.test.d.ts +2 -0
  120. package/dist/tests/units/modules/ember-chat/gateways/emberStreamJson.parser.test.d.ts.map +1 -0
  121. package/dist/tests/units/modules/ember-chat/gateways/emberStreamJson.parser.test.js +52 -0
  122. package/dist/tests/units/modules/ember-chat/gateways/emberStreamJson.parser.test.js.map +1 -0
  123. package/dist/tests/units/modules/ember-chat/presenters/emberStatus.presenter.test.d.ts +2 -0
  124. package/dist/tests/units/modules/ember-chat/presenters/emberStatus.presenter.test.d.ts.map +1 -0
  125. package/dist/tests/units/modules/ember-chat/presenters/emberStatus.presenter.test.js +27 -0
  126. package/dist/tests/units/modules/ember-chat/presenters/emberStatus.presenter.test.js.map +1 -0
  127. package/dist/tests/units/modules/ember-chat/services/emberSystemPrompt.test.d.ts +2 -0
  128. package/dist/tests/units/modules/ember-chat/services/emberSystemPrompt.test.d.ts.map +1 -0
  129. package/dist/tests/units/modules/ember-chat/services/emberSystemPrompt.test.js +41 -0
  130. package/dist/tests/units/modules/ember-chat/services/emberSystemPrompt.test.js.map +1 -0
  131. package/dist/tests/units/modules/ember-chat/usecases/askEmber.usecase.test.d.ts +2 -0
  132. package/dist/tests/units/modules/ember-chat/usecases/askEmber.usecase.test.d.ts.map +1 -0
  133. package/dist/tests/units/modules/ember-chat/usecases/askEmber.usecase.test.js +46 -0
  134. package/dist/tests/units/modules/ember-chat/usecases/askEmber.usecase.test.js.map +1 -0
  135. package/dist/tests/units/modules/ember-chat/usecases/emberSessionRegistry.test.d.ts +2 -0
  136. package/dist/tests/units/modules/ember-chat/usecases/emberSessionRegistry.test.d.ts.map +1 -0
  137. package/dist/tests/units/modules/ember-chat/usecases/emberSessionRegistry.test.js +88 -0
  138. package/dist/tests/units/modules/ember-chat/usecases/emberSessionRegistry.test.js.map +1 -0
  139. package/package.json +1 -1
@@ -0,0 +1,36 @@
1
+ import type { EmberSessionTransportGateway } from '../../../../modules/ember-chat/entities/emberSession/emberSessionTransport.gateway.js';
2
+ export type EmberStatus = 'working' | 'idle' | 'error';
3
+ export interface EmberStreamSubscriber {
4
+ onStatus: (status: EmberStatus) => void;
5
+ onChunk: (text: string) => void;
6
+ onDone: () => void;
7
+ onError: (message: string) => void;
8
+ }
9
+ export type EmberAskResult = {
10
+ status: 'streaming';
11
+ subscribe: (subscriber: EmberStreamSubscriber) => void;
12
+ } | {
13
+ status: 'unavailable';
14
+ reason: string;
15
+ };
16
+ export interface EmberAskRequest {
17
+ question: string;
18
+ systemPrompt: string;
19
+ projectPath: string;
20
+ }
21
+ export interface EmberSessionRegistryDependencies {
22
+ transport: EmberSessionTransportGateway;
23
+ now: () => Date;
24
+ idleTimeoutMs: number;
25
+ }
26
+ export declare class EmberSessionRegistry {
27
+ private readonly dependencies;
28
+ private handle;
29
+ private state;
30
+ constructor(dependencies: EmberSessionRegistryDependencies);
31
+ ask(request: EmberAskRequest): EmberAskResult;
32
+ onIdle(now: Date): void;
33
+ private ensureLive;
34
+ private release;
35
+ }
36
+ //# sourceMappingURL=emberSessionRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emberSessionRegistry.d.ts","sourceRoot":"","sources":["../../../../../src/modules/ember-chat/usecases/emberSession/emberSessionRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,4BAA4B,EAC7B,MAAM,6EAA6E,CAAC;AAIrF,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;AAEvD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IACxC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,CAAC,UAAU,EAAE,qBAAqB,KAAK,IAAI,CAAA;CAAE,GAC/E;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9C,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gCAAgC;IAC/C,SAAS,EAAE,4BAA4B,CAAC;IACxC,GAAG,EAAE,MAAM,IAAI,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,oBAAoB;IAInB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAHzC,OAAO,CAAC,MAAM,CAAmC;IACjD,OAAO,CAAC,KAAK,CAAoD;gBAEpC,YAAY,EAAE,gCAAgC;IAE3E,GAAG,CAAC,OAAO,EAAE,eAAe,GAAG,cAAc;IA2B7C,MAAM,CAAC,GAAG,EAAE,IAAI,GAAG,IAAI;IAQvB,OAAO,CAAC,UAAU;IAelB,OAAO,CAAC,OAAO;CAMhB"}
@@ -0,0 +1,59 @@
1
+ import { createIdleEmberSessionState } from '../../../../modules/ember-chat/entities/emberSession/emberSessionState.js';
2
+ export class EmberSessionRegistry {
3
+ dependencies;
4
+ handle = null;
5
+ state = createIdleEmberSessionState();
6
+ constructor(dependencies) {
7
+ this.dependencies = dependencies;
8
+ }
9
+ ask(request) {
10
+ const handle = this.ensureLive(request.systemPrompt, request.projectPath);
11
+ if (handle === null) {
12
+ return { status: 'unavailable', reason: 'spawn-failed' };
13
+ }
14
+ this.state = this.state.onQuestion(this.dependencies.now());
15
+ return {
16
+ status: 'streaming',
17
+ subscribe: (subscriber) => {
18
+ subscriber.onStatus('working');
19
+ handle.onChunk((text) => subscriber.onChunk(text));
20
+ handle.onError((message) => {
21
+ subscriber.onStatus('error');
22
+ subscriber.onError(message);
23
+ });
24
+ handle.onDone(() => {
25
+ this.state = this.state.onAnswerDone(this.dependencies.now());
26
+ subscriber.onStatus('idle');
27
+ subscriber.onDone();
28
+ });
29
+ handle.ask(request.question);
30
+ },
31
+ };
32
+ }
33
+ onIdle(now) {
34
+ const next = this.state.onIdleTick(now, this.dependencies.idleTimeoutMs);
35
+ if (next.phase === 'idle' && this.state.phase === 'live') {
36
+ this.release();
37
+ }
38
+ this.state = next;
39
+ }
40
+ ensureLive(systemPrompt, projectPath) {
41
+ if (this.handle?.isAlive() === true && !this.state.needsProcess()) {
42
+ return this.handle;
43
+ }
44
+ const result = this.dependencies.transport.spawn({ systemPrompt, projectPath });
45
+ if (result.status === 'failed') {
46
+ this.handle = null;
47
+ return null;
48
+ }
49
+ this.handle = result.handle;
50
+ return this.handle;
51
+ }
52
+ release() {
53
+ if (this.handle !== null) {
54
+ this.handle.kill();
55
+ this.handle = null;
56
+ }
57
+ }
58
+ }
59
+ //# sourceMappingURL=emberSessionRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emberSessionRegistry.js","sourceRoot":"","sources":["../../../../../src/modules/ember-chat/usecases/emberSession/emberSessionRegistry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=189-ember-readonly-review-chat.acceptance.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"189-ember-readonly-review-chat.acceptance.test.d.ts","sourceRoot":"","sources":["../../../src/tests/acceptance/189-ember-readonly-review-chat.acceptance.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,124 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ProjectStatsFactory, ReviewStatsFactory } from '../../tests/factories/projectStats.factory.js';
3
+ import { StubEnvironmentGateway } from '../../tests/stubs/environment.stub.js';
4
+ import { StubEmberReadDataGateway } from '../../tests/stubs/emberReadData.stub.js';
5
+ import { StubEmberSessionTransportGateway } from '../../tests/stubs/emberSessionTransport.stub.js';
6
+ import { EmberSessionRegistry } from '../../modules/ember-chat/usecases/emberSession/emberSessionRegistry.js';
7
+ import { askEmber } from '../../modules/ember-chat/usecases/askEmber/askEmber.usecase.js';
8
+ const PROJECT_PATH = '/projects/alpha';
9
+ const FIXED_NOW = () => new Date('2026-05-28T10:00:00Z');
10
+ function fixedReviewData() {
11
+ const worstProject = ProjectStatsFactory.withReviews([
12
+ ReviewStatsFactory.create({ mrNumber: 42, score: 3, blocking: 4, warnings: 1 }),
13
+ ReviewStatsFactory.create({ mrNumber: 43, score: 4, blocking: 2, warnings: 0 }),
14
+ ]);
15
+ const readData = new StubEmberReadDataGateway();
16
+ readData.setReviewScores(PROJECT_PATH, worstProject);
17
+ return readData;
18
+ }
19
+ function buildRegistry(transport) {
20
+ return new EmberSessionRegistry({ transport, now: FIXED_NOW, idleTimeoutMs: 60_000 });
21
+ }
22
+ function buildEnvironment(hasApiKey) {
23
+ const environment = new StubEnvironmentGateway();
24
+ environment.setHasAnthropicApiKey(hasApiKey);
25
+ return environment;
26
+ }
27
+ function collectStream(subscribe) {
28
+ return new Promise((resolve) => {
29
+ let answer = '';
30
+ const statuses = [];
31
+ subscribe({
32
+ onStatus: (state) => {
33
+ statuses.push(state);
34
+ },
35
+ onChunk: (text) => {
36
+ answer += text;
37
+ },
38
+ onDone: () => {
39
+ resolve({ answer, statuses });
40
+ },
41
+ onError: () => {
42
+ resolve({ answer, statuses });
43
+ },
44
+ });
45
+ });
46
+ }
47
+ describe('Ask Ember about your reviews (acceptance, SPEC-189 Phase A)', () => {
48
+ describe('Ember answers from review data with a working then idle status sequence', () => {
49
+ it('ask about scores: the answer is grounded in the current review scores', async () => {
50
+ const readData = fixedReviewData();
51
+ const transport = new StubEmberSessionTransportGateway();
52
+ transport.answerFromSystemPrompt();
53
+ const result = await askEmber({ question: 'Quel projet a le pire score moyen cette semaine ?' }, {
54
+ registry: buildRegistry(transport),
55
+ environment: buildEnvironment(false),
56
+ readData,
57
+ projectPath: PROJECT_PATH,
58
+ now: FIXED_NOW,
59
+ });
60
+ expect(result.status).toBe('streaming');
61
+ if (result.status !== 'streaming') {
62
+ return;
63
+ }
64
+ const { answer, statuses } = await collectStream(result.subscribe);
65
+ // "42" reaches the answer only by traversing the production grounding path:
66
+ // readData -> askEmber -> system prompt -> session. The stub echoes the prompt.
67
+ expect(answer).toContain('42');
68
+ expect(statuses[0]).toBe('working');
69
+ expect(statuses.at(-1)).toBe('idle');
70
+ });
71
+ });
72
+ describe('Ember requires no API key', () => {
73
+ it('billing regression: declines to spawn when an Anthropic API key is present', async () => {
74
+ const transport = new StubEmberSessionTransportGateway();
75
+ transport.answerFromSystemPrompt();
76
+ const result = await askEmber({ question: 'Quel projet a le pire score moyen cette semaine ?' }, {
77
+ registry: buildRegistry(transport),
78
+ environment: buildEnvironment(true),
79
+ readData: fixedReviewData(),
80
+ projectPath: PROJECT_PATH,
81
+ now: FIXED_NOW,
82
+ });
83
+ expect(result.status).toBe('billing-regression-prevented');
84
+ expect(transport.spawnCount).toBe(0);
85
+ });
86
+ });
87
+ describe('within a session, consecutive questions reuse one Ember', () => {
88
+ it('follow-up keeps context: a second question does not cold-start a new session', async () => {
89
+ const transport = new StubEmberSessionTransportGateway();
90
+ transport.answerFromSystemPrompt();
91
+ const deps = {
92
+ registry: buildRegistry(transport),
93
+ environment: buildEnvironment(false),
94
+ readData: fixedReviewData(),
95
+ projectPath: PROJECT_PATH,
96
+ now: FIXED_NOW,
97
+ };
98
+ const first = await askEmber({ question: 'Quel projet a le pire score moyen ?' }, deps);
99
+ if (first.status === 'streaming') {
100
+ await collectStream(first.subscribe);
101
+ }
102
+ const second = await askEmber({ question: 'Et le mois dernier ?' }, deps);
103
+ if (second.status === 'streaming') {
104
+ await collectStream(second.subscribe);
105
+ }
106
+ expect(transport.spawnCount).toBe(1);
107
+ });
108
+ });
109
+ describe('when Ember cannot be reached, the chat reports unavailability', () => {
110
+ it('assistant unreachable: returns unavailable when the transport fails to spawn', async () => {
111
+ const transport = new StubEmberSessionTransportGateway();
112
+ transport.failSpawn();
113
+ const result = await askEmber({ question: 'Quel projet a le pire score moyen ?' }, {
114
+ registry: buildRegistry(transport),
115
+ environment: buildEnvironment(false),
116
+ readData: fixedReviewData(),
117
+ projectPath: PROJECT_PATH,
118
+ now: FIXED_NOW,
119
+ });
120
+ expect(result.status).toBe('unavailable');
121
+ });
122
+ });
123
+ });
124
+ //# sourceMappingURL=189-ember-readonly-review-chat.acceptance.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"189-ember-readonly-review-chat.acceptance.test.js","sourceRoot":"","sources":["../../../src/tests/acceptance/189-ember-readonly-review-chat.acceptance.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { EmberMessage } from '../../modules/ember-chat/entities/emberMessage/emberMessage.schema.js';
2
+ export declare class EmberMessageFactory {
3
+ static create(overrides?: Partial<EmberMessage>): EmberMessage;
4
+ }
5
+ //# sourceMappingURL=emberMessage.factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emberMessage.factory.d.ts","sourceRoot":"","sources":["../../../src/tests/factories/emberMessage.factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mEAAmE,CAAC;AAEtG,qBAAa,mBAAmB;IAC9B,MAAM,CAAC,MAAM,CAAC,SAAS,GAAE,OAAO,CAAC,YAAY,CAAM,GAAG,YAAY;CAMnE"}
@@ -0,0 +1,9 @@
1
+ export class EmberMessageFactory {
2
+ static create(overrides = {}) {
3
+ return {
4
+ question: 'Quel projet a le pire score moyen cette semaine ?',
5
+ ...overrides,
6
+ };
7
+ }
8
+ }
9
+ //# sourceMappingURL=emberMessage.factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emberMessage.factory.js","sourceRoot":"","sources":["../../../src/tests/factories/emberMessage.factory.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,mBAAmB;IAC9B,MAAM,CAAC,MAAM,CAAC,YAAmC,EAAE;QACjD,OAAO;YACL,QAAQ,EAAE,mDAAmD;YAC7D,GAAG,SAAS;SACb,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,20 @@
1
+ import type { EmberReadDataGateway } from '../../modules/ember-chat/entities/emberTool/emberTool.gateway.js';
2
+ import type { ProjectStats } from '../../modules/statistics-insights/entities/stats/projectStats.js';
3
+ import type { PersistedInsightsData } from '../../modules/statistics-insights/entities/insight/persistedInsightsData.js';
4
+ import type { MrTrackingData } from '../../modules/tracking/entities/tracking/mrTrackingData.js';
5
+ import type { WorktreeEntry } from '../../modules/worktree-management/entities/worktree/worktree.schema.js';
6
+ export declare class StubEmberReadDataGateway implements EmberReadDataGateway {
7
+ private reviewScoresByProject;
8
+ private insightsByProject;
9
+ private jobHistoryByProject;
10
+ private worktreeEntries;
11
+ setReviewScores(projectPath: string, stats: ProjectStats): void;
12
+ setInsights(projectPath: string, data: PersistedInsightsData): void;
13
+ setJobHistory(projectPath: string, data: MrTrackingData): void;
14
+ setWorktrees(entries: WorktreeEntry[]): void;
15
+ reviewScores(projectPath: string): Promise<ProjectStats | null>;
16
+ insights(projectPath: string): Promise<PersistedInsightsData | null>;
17
+ jobHistory(projectPath: string): Promise<MrTrackingData | null>;
18
+ worktrees(): Promise<WorktreeEntry[]>;
19
+ }
20
+ //# sourceMappingURL=emberReadData.stub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emberReadData.stub.d.ts","sourceRoot":"","sources":["../../../src/tests/stubs/emberReadData.stub.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,8DAA8D,CAAC;AACzG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8DAA8D,CAAC;AACjG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yEAAyE,CAAC;AACrH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wDAAwD,CAAC;AAC7F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oEAAoE,CAAC;AAExG,qBAAa,wBAAyB,YAAW,oBAAoB;IACnE,OAAO,CAAC,qBAAqB,CAAmC;IAChE,OAAO,CAAC,iBAAiB,CAA4C;IACrE,OAAO,CAAC,mBAAmB,CAAqC;IAChE,OAAO,CAAC,eAAe,CAAuB;IAE9C,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI;IAI/D,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,qBAAqB,GAAG,IAAI;IAInE,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,IAAI;IAI9D,YAAY,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,IAAI;IAItC,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAI/D,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC;IAIpE,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAI/D,SAAS,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;CAG5C"}
@@ -0,0 +1,31 @@
1
+ export class StubEmberReadDataGateway {
2
+ reviewScoresByProject = new Map();
3
+ insightsByProject = new Map();
4
+ jobHistoryByProject = new Map();
5
+ worktreeEntries = [];
6
+ setReviewScores(projectPath, stats) {
7
+ this.reviewScoresByProject.set(projectPath, stats);
8
+ }
9
+ setInsights(projectPath, data) {
10
+ this.insightsByProject.set(projectPath, data);
11
+ }
12
+ setJobHistory(projectPath, data) {
13
+ this.jobHistoryByProject.set(projectPath, data);
14
+ }
15
+ setWorktrees(entries) {
16
+ this.worktreeEntries = entries;
17
+ }
18
+ async reviewScores(projectPath) {
19
+ return this.reviewScoresByProject.get(projectPath) ?? null;
20
+ }
21
+ async insights(projectPath) {
22
+ return this.insightsByProject.get(projectPath) ?? null;
23
+ }
24
+ async jobHistory(projectPath) {
25
+ return this.jobHistoryByProject.get(projectPath) ?? null;
26
+ }
27
+ async worktrees() {
28
+ return this.worktreeEntries;
29
+ }
30
+ }
31
+ //# sourceMappingURL=emberReadData.stub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emberReadData.stub.js","sourceRoot":"","sources":["../../../src/tests/stubs/emberReadData.stub.ts"],"names":[],"mappings":"AAMA,MAAM,OAAO,wBAAwB;IAC3B,qBAAqB,GAAG,IAAI,GAAG,EAAwB,CAAC;IACxD,iBAAiB,GAAG,IAAI,GAAG,EAAiC,CAAC;IAC7D,mBAAmB,GAAG,IAAI,GAAG,EAA0B,CAAC;IACxD,eAAe,GAAoB,EAAE,CAAC;IAE9C,eAAe,CAAC,WAAmB,EAAE,KAAmB;QACtD,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IACrD,CAAC;IAED,WAAW,CAAC,WAAmB,EAAE,IAA2B;QAC1D,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IAChD,CAAC;IAED,aAAa,CAAC,WAAmB,EAAE,IAAoB;QACrD,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,YAAY,CAAC,OAAwB;QACnC,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,WAAmB;QACpC,OAAO,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC;IAC7D,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,WAAmB;QAChC,OAAO,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC;IACzD,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,WAAmB;QAClC,OAAO,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC;IAC3D,CAAC;IAED,KAAK,CAAC,SAAS;QACb,OAAO,IAAI,CAAC,eAAe,CAAC;IAC9B,CAAC;CACF"}
@@ -0,0 +1,18 @@
1
+ import type { EmberSessionSpawnOptions, EmberSessionSpawnResult, EmberSessionTransportGateway } from '../../modules/ember-chat/entities/emberSession/emberSessionTransport.gateway.js';
2
+ type AnswerBuilder = (question: string, systemPrompt: string) => Promise<string>;
3
+ export declare class StubEmberSessionTransportGateway implements EmberSessionTransportGateway {
4
+ spawnCount: number;
5
+ private shouldFail;
6
+ private answerBuilder;
7
+ failSpawn(): void;
8
+ respondWith(builder: AnswerBuilder): void;
9
+ /**
10
+ * Makes the stub answer with the system prompt it was spawned with. The grounding
11
+ * data lives in that prompt, so this proves the real path: readData → askEmber →
12
+ * prompt → session, rather than the stub fabricating an answer of its own.
13
+ */
14
+ answerFromSystemPrompt(): void;
15
+ spawn(options: EmberSessionSpawnOptions): EmberSessionSpawnResult;
16
+ }
17
+ export {};
18
+ //# sourceMappingURL=emberSessionTransport.stub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emberSessionTransport.stub.d.ts","sourceRoot":"","sources":["../../../src/tests/stubs/emberSessionTransport.stub.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAKV,wBAAwB,EACxB,uBAAuB,EACvB,4BAA4B,EAC7B,MAAM,6EAA6E,CAAC;AAErF,KAAK,aAAa,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAqDjF,qBAAa,gCAAiC,YAAW,4BAA4B;IACnF,UAAU,SAAK;IACf,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,aAAa,CAAgE;IAErF,SAAS,IAAI,IAAI;IAIjB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAIzC;;;;OAIG;IACH,sBAAsB,IAAI,IAAI;IAI9B,KAAK,CAAC,OAAO,EAAE,wBAAwB,GAAG,uBAAuB;CAUlE"}
@@ -0,0 +1,75 @@
1
+ class StubEmberSessionHandle {
2
+ answerBuilder;
3
+ systemPrompt;
4
+ chunkHandler = null;
5
+ doneHandler = null;
6
+ errorHandler = null;
7
+ alive = true;
8
+ constructor(answerBuilder, systemPrompt) {
9
+ this.answerBuilder = answerBuilder;
10
+ this.systemPrompt = systemPrompt;
11
+ }
12
+ ask(question) {
13
+ void this.deliver(question);
14
+ }
15
+ onChunk(handler) {
16
+ this.chunkHandler = handler;
17
+ }
18
+ onDone(handler) {
19
+ this.doneHandler = handler;
20
+ }
21
+ onError(handler) {
22
+ this.errorHandler = handler;
23
+ }
24
+ isAlive() {
25
+ return this.alive;
26
+ }
27
+ kill() {
28
+ this.alive = false;
29
+ }
30
+ async deliver(question) {
31
+ try {
32
+ const answer = await this.answerBuilder(question, this.systemPrompt);
33
+ const words = answer.split(' ');
34
+ for (let index = 0; index < words.length; index += 1) {
35
+ const fragment = index === 0 ? words[index] : ` ${words[index]}`;
36
+ this.chunkHandler?.(fragment);
37
+ }
38
+ this.doneHandler?.();
39
+ }
40
+ catch (error) {
41
+ const message = error instanceof Error ? error.message : 'stub-answer-failed';
42
+ this.errorHandler?.(message);
43
+ }
44
+ }
45
+ }
46
+ export class StubEmberSessionTransportGateway {
47
+ spawnCount = 0;
48
+ shouldFail = false;
49
+ answerBuilder = async (question) => `Réponse à : ${question}`;
50
+ failSpawn() {
51
+ this.shouldFail = true;
52
+ }
53
+ respondWith(builder) {
54
+ this.answerBuilder = builder;
55
+ }
56
+ /**
57
+ * Makes the stub answer with the system prompt it was spawned with. The grounding
58
+ * data lives in that prompt, so this proves the real path: readData → askEmber →
59
+ * prompt → session, rather than the stub fabricating an answer of its own.
60
+ */
61
+ answerFromSystemPrompt() {
62
+ this.answerBuilder = async (_question, systemPrompt) => systemPrompt;
63
+ }
64
+ spawn(options) {
65
+ if (this.shouldFail) {
66
+ return { status: 'failed', reason: 'stub-spawn-failed' };
67
+ }
68
+ this.spawnCount += 1;
69
+ return {
70
+ status: 'spawned',
71
+ handle: new StubEmberSessionHandle(this.answerBuilder, options.systemPrompt),
72
+ };
73
+ }
74
+ }
75
+ //# sourceMappingURL=emberSessionTransport.stub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emberSessionTransport.stub.js","sourceRoot":"","sources":["../../../src/tests/stubs/emberSessionTransport.stub.ts"],"names":[],"mappings":"AAYA,MAAM,sBAAsB;IAOP;IACA;IAPX,YAAY,GAA6B,IAAI,CAAC;IAC9C,WAAW,GAA4B,IAAI,CAAC;IAC5C,YAAY,GAA6B,IAAI,CAAC;IAC9C,KAAK,GAAG,IAAI,CAAC;IAErB,YACmB,aAA4B,EAC5B,YAAoB;QADpB,kBAAa,GAAb,aAAa,CAAe;QAC5B,iBAAY,GAAZ,YAAY,CAAQ;IACpC,CAAC;IAEJ,GAAG,CAAC,QAAgB;QAClB,KAAK,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC;IAED,OAAO,CAAC,OAA0B;QAChC,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC;IAC9B,CAAC;IAED,MAAM,CAAC,OAAyB;QAC9B,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC;IAC7B,CAAC;IAED,OAAO,CAAC,OAA0B;QAChC,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC;IAC9B,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,IAAI;QACF,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAEO,KAAK,CAAC,OAAO,CAAC,QAAgB;QACpC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;YACrE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAChC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;gBACrD,MAAM,QAAQ,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;gBACjE,IAAI,CAAC,YAAY,EAAE,CAAC,QAAQ,CAAC,CAAC;YAChC,CAAC;YACD,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;QACvB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,oBAAoB,CAAC;YAC9E,IAAI,CAAC,YAAY,EAAE,CAAC,OAAO,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;CACF;AAED,MAAM,OAAO,gCAAgC;IAC3C,UAAU,GAAG,CAAC,CAAC;IACP,UAAU,GAAG,KAAK,CAAC;IACnB,aAAa,GAAkB,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,eAAe,QAAQ,EAAE,CAAC;IAErF,SAAS;QACP,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;IACzB,CAAC;IAED,WAAW,CAAC,OAAsB;QAChC,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC;IAC/B,CAAC;IAED;;;;OAIG;IACH,sBAAsB;QACpB,IAAI,CAAC,aAAa,GAAG,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,EAAE,CAAC,YAAY,CAAC;IACvE,CAAC;IAED,KAAK,CAAC,OAAiC;QACrC,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;QAC3D,CAAC;QACD,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC;QACrB,OAAO;YACL,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,IAAI,sBAAsB,CAAC,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,YAAY,CAAC;SAC7E,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=emberAvatar.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emberAvatar.test.d.ts","sourceRoot":"","sources":["../../../../../src/tests/units/dashboard/modules/emberAvatar.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,74 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { EMBER_STATES, emberStateToVisual, buildFlameWireframe, projectFlameVertex, emberRadiusFactor, emberSwayOffset, FLAME_TIP_Y, FLAME_BASE_Y, } from '../../../../dashboard/modules/emberAvatar.js';
3
+ describe('EMBER_STATES', () => {
4
+ it('exposes exactly the three chat-driven states in narrative order', () => {
5
+ expect(EMBER_STATES).toEqual(['idle', 'working', 'error']);
6
+ });
7
+ });
8
+ describe('emberStateToVisual', () => {
9
+ it('makes the working state lean, flicker and glow harder than idle', () => {
10
+ const idle = emberStateToVisual('idle');
11
+ const working = emberStateToVisual('working');
12
+ expect(working.swayAmount).toBeGreaterThan(idle.swayAmount);
13
+ expect(working.flicker).toBeGreaterThan(idle.flicker);
14
+ expect(working.glow).toBeGreaterThan(idle.glow);
15
+ });
16
+ it('paints the error state with a distinct colour token from idle', () => {
17
+ expect(emberStateToVisual('error').color).not.toBe(emberStateToVisual('idle').color);
18
+ });
19
+ });
20
+ describe('buildFlameWireframe', () => {
21
+ it('has one tip vertex plus a rings × meridians body', () => {
22
+ const { vertices } = buildFlameWireframe({ rings: 6, meridians: 8 });
23
+ expect(vertices).toHaveLength(1 + 6 * 8);
24
+ expect(vertices[0]).toEqual([0, FLAME_TIP_Y, 0]);
25
+ for (const vertex of vertices) {
26
+ expect(vertex).toHaveLength(3);
27
+ }
28
+ });
29
+ it('connects the tip, the meridians and the ring loops with edges', () => {
30
+ const { edges } = buildFlameWireframe({ rings: 6, meridians: 8 });
31
+ const tipSpokes = 8;
32
+ const verticalLines = (6 - 1) * 8;
33
+ const ringLoops = 6 * 8;
34
+ expect(edges).toHaveLength(tipSpokes + verticalLines + ringLoops);
35
+ });
36
+ });
37
+ describe('projectFlameVertex', () => {
38
+ const projection = { tilt: 0.12, distance: 4, scale: 50, centerX: 120, centerY: 120 };
39
+ it('is deterministic for the same inputs', () => {
40
+ const vertex = [0.5, 0.2, -0.3];
41
+ expect(projectFlameVertex(vertex, 1.1, 0.2, projection)).toEqual(projectFlameVertex(vertex, 1.1, 0.2, projection));
42
+ });
43
+ it('leans the tip far more than the base for a positive sway', () => {
44
+ const tip = [0, FLAME_TIP_Y, 0];
45
+ const base = [0, FLAME_BASE_Y, 0];
46
+ const tipShift = projectFlameVertex(tip, 0, 0.4, projection).x - projectFlameVertex(tip, 0, 0, projection).x;
47
+ const baseShift = projectFlameVertex(base, 0, 0.4, projection).x - projectFlameVertex(base, 0, 0, projection).x;
48
+ expect(tipShift).toBeGreaterThan(baseShift);
49
+ expect(baseShift).toBeCloseTo(0, 6);
50
+ });
51
+ });
52
+ describe('emberRadiusFactor', () => {
53
+ it('returns the neutral factor of 1 at time 0', () => {
54
+ expect(emberRadiusFactor(emberStateToVisual('idle'), 0)).toBeCloseTo(1, 5);
55
+ });
56
+ it('stays within the flicker envelope', () => {
57
+ const visual = emberStateToVisual('working');
58
+ for (const time of [120, 900, 1750, 5000]) {
59
+ const factor = emberRadiusFactor(visual, time);
60
+ expect(factor).toBeGreaterThanOrEqual(1 - visual.flicker - 1e-6);
61
+ expect(factor).toBeLessThanOrEqual(1 + visual.flicker + 1e-6);
62
+ }
63
+ });
64
+ });
65
+ describe('emberSwayOffset', () => {
66
+ it('is zero at time 0 and bounded by the sway amplitude', () => {
67
+ const visual = emberStateToVisual('working');
68
+ expect(emberSwayOffset(visual, 0)).toBeCloseTo(0, 6);
69
+ for (const time of [200, 1234, 4321]) {
70
+ expect(Math.abs(emberSwayOffset(visual, time))).toBeLessThanOrEqual(visual.swayAmount + 1e-6);
71
+ }
72
+ });
73
+ });
74
+ //# sourceMappingURL=emberAvatar.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emberAvatar.test.js","sourceRoot":"","sources":["../../../../../src/tests/units/dashboard/modules/emberAvatar.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,eAAe,EACf,WAAW,EACX,YAAY,GACb,MAAM,oCAAoC,CAAC;AAE5C,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QACzE,MAAM,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QACzE,MAAM,IAAI,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;QACxC,MAAM,OAAO,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;QAC9C,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC5D,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtD,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC;IACvF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,EAAE,QAAQ,EAAE,GAAG,mBAAmB,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC;QACrE,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QACzC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC;QACjD,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;YAC9B,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACjC,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,EAAE,KAAK,EAAE,GAAG,mBAAmB,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC;QAClE,MAAM,SAAS,GAAG,CAAC,CAAC;QACpB,MAAM,aAAa,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,SAAS,GAAG,CAAC,GAAG,CAAC,CAAC;QACxB,MAAM,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,SAAS,GAAG,aAAa,GAAG,SAAS,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,MAAM,UAAU,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;IAEtF,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,MAAM,GAA6B,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;QAC1D,MAAM,CAAC,kBAAkB,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,OAAO,CAC9D,kBAAkB,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,UAAU,CAAC,CACjD,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,GAAG,GAA6B,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;QAC1D,MAAM,IAAI,GAA6B,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;QAC5D,MAAM,QAAQ,GAAG,kBAAkB,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;QAC7G,MAAM,SAAS,GAAG,kBAAkB,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;QAChH,MAAM,CAAC,QAAQ,CAAC,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;QAC5C,MAAM,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,MAAM,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;QAC7C,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YAC/C,MAAM,CAAC,MAAM,CAAC,CAAC,sBAAsB,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;YACjE,MAAM,CAAC,MAAM,CAAC,CAAC,mBAAmB,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;QAChE,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,MAAM,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;QAC7C,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACrD,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;QAChG,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=emberAvatarRenderer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emberAvatarRenderer.test.d.ts","sourceRoot":"","sources":["../../../../../src/tests/units/dashboard/modules/emberAvatarRenderer.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,44 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mountEmberAvatar } from '../../../../dashboard/modules/emberAvatarRenderer.js';
3
+ function fakeCanvas() {
4
+ return { width: 240, height: 240, getContext: () => null };
5
+ }
6
+ describe('mountEmberAvatar (lifecycle glue)', () => {
7
+ it('schedules an animation frame on mount', () => {
8
+ const requested = [];
9
+ mountEmberAvatar({
10
+ canvas: fakeCanvas(),
11
+ requestFrame: (callback) => {
12
+ requested.push(callback);
13
+ return requested.length;
14
+ },
15
+ cancelFrame: () => undefined,
16
+ now: () => 0,
17
+ });
18
+ expect(requested).toHaveLength(1);
19
+ });
20
+ it('cancels the scheduled frame on destroy so the loop cannot leak', () => {
21
+ const cancelled = [];
22
+ const controls = mountEmberAvatar({
23
+ canvas: fakeCanvas(),
24
+ requestFrame: () => 42,
25
+ cancelFrame: (handle) => {
26
+ cancelled.push(handle);
27
+ },
28
+ now: () => 0,
29
+ });
30
+ controls.destroy();
31
+ expect(cancelled).toEqual([42]);
32
+ });
33
+ it('exposes setState and destroy controls', () => {
34
+ const controls = mountEmberAvatar({
35
+ canvas: fakeCanvas(),
36
+ requestFrame: () => 1,
37
+ cancelFrame: () => undefined,
38
+ now: () => 0,
39
+ });
40
+ expect(typeof controls.setState).toBe('function');
41
+ expect(typeof controls.destroy).toBe('function');
42
+ });
43
+ });
44
+ //# sourceMappingURL=emberAvatarRenderer.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emberAvatarRenderer.test.js","sourceRoot":"","sources":["../../../../../src/tests/units/dashboard/modules/emberAvatarRenderer.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,4CAA4C,CAAC;AAQ9E,SAAS,UAAU;IACjB,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC;AAC7D,CAAC;AAED,QAAQ,CAAC,mCAAmC,EAAE,GAAG,EAAE;IACjD,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,SAAS,GAAkC,EAAE,CAAC;QAEpD,gBAAgB,CAAC;YACf,MAAM,EAAE,UAAU,EAAE;YACpB,YAAY,EAAE,CAAC,QAAQ,EAAE,EAAE;gBACzB,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACzB,OAAO,SAAS,CAAC,MAAM,CAAC;YAC1B,CAAC;YACD,WAAW,EAAE,GAAG,EAAE,CAAC,SAAS;YAC5B,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;SACb,CAAC,CAAC;QAEH,MAAM,CAAC,SAAS,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,MAAM,SAAS,GAAa,EAAE,CAAC;QAE/B,MAAM,QAAQ,GAAG,gBAAgB,CAAC;YAChC,MAAM,EAAE,UAAU,EAAE;YACpB,YAAY,EAAE,GAAG,EAAE,CAAC,EAAE;YACtB,WAAW,EAAE,CAAC,MAAM,EAAE,EAAE;gBACtB,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACzB,CAAC;YACD,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;SACb,CAAC,CAAC;QAEH,QAAQ,CAAC,OAAO,EAAE,CAAC;QAEnB,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,QAAQ,GAAG,gBAAgB,CAAC;YAChC,MAAM,EAAE,UAAU,EAAE;YACpB,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC;YACrB,WAAW,EAAE,GAAG,EAAE,CAAC,SAAS;YAC5B,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;SACb,CAAC,CAAC;QAEH,MAAM,CAAC,OAAO,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAClD,MAAM,CAAC,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=emberChat.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emberChat.test.d.ts","sourceRoot":"","sources":["../../../../../src/tests/units/dashboard/modules/emberChat.test.ts"],"names":[],"mappings":""}