@@ -64,9 +64,57 @@ const configResponse = {
6464 routing : {
6565 modelCards : [ { name : 'test-model' } ] ,
6666 signals : {
67- domains : [ { name : 'business' , description : 'Business' } ] ,
67+ domains : [
68+ { name : 'business' , description : 'Business' } ,
69+ { name : 'economics' , description : 'Economics' } ,
70+ ] ,
71+ embeddings : [
72+ {
73+ name : 'business_analysis' ,
74+ threshold : 0.72 ,
75+ candidates : [ 'business' ] ,
76+ aggregation_method : 'max' ,
77+ } ,
78+ ] ,
79+ preferences : [
80+ {
81+ name : 'structured_delivery' ,
82+ threshold : 0.55 ,
83+ examples : [ 'Use a structured answer' ] ,
84+ } ,
85+ ] ,
86+ complexity : [
87+ { name : 'general_reasoning:easy' , threshold : 0.25 } ,
88+ { name : 'general_reasoning:medium' , threshold : 0.5 } ,
89+ ] ,
6890 } ,
6991 decisions : [
92+ {
93+ name : 'medium_business' ,
94+ priority : 215 ,
95+ rules : {
96+ operator : 'AND' ,
97+ conditions : [
98+ {
99+ operator : 'OR' ,
100+ conditions : [
101+ { type : 'domain' , name : 'business' } ,
102+ { type : 'domain' , name : 'economics' } ,
103+ ] ,
104+ } ,
105+ {
106+ operator : 'OR' ,
107+ conditions : [
108+ { type : 'embedding' , name : 'business_analysis' } ,
109+ { type : 'complexity' , name : 'general_reasoning:easy' } ,
110+ { type : 'complexity' , name : 'general_reasoning:medium' } ,
111+ { type : 'preference' , name : 'structured_delivery' } ,
112+ ] ,
113+ } ,
114+ ] ,
115+ } ,
116+ modelRefs : [ { model : 'test-model' , use_reasoning : true , reasoning_effort : 'medium' } ] ,
117+ } ,
70118 {
71119 name : 'business-route' ,
72120 priority : 1 ,
@@ -112,6 +160,109 @@ const statusResponse = {
112160 } ,
113161} ;
114162
163+ const replayRecordsResponse = {
164+ object : 'router_replay.list' ,
165+ count : 2 ,
166+ total : 2 ,
167+ limit : 100 ,
168+ offset : 0 ,
169+ has_more : false ,
170+ data : [
171+ {
172+ id : 'replay-1' ,
173+ timestamp : '2026-03-17T10:00:00Z' ,
174+ request_id : 'req-1' ,
175+ decision : 'business-route' ,
176+ original_model : 'test-model' ,
177+ selected_model : 'test-model' ,
178+ reasoning_mode : 'off' ,
179+ selection_method : 'static' ,
180+ signals : {
181+ domain : [ 'business' ] ,
182+ keyword : [ 'finance' ] ,
183+ } ,
184+ response_status : 200 ,
185+ from_cache : false ,
186+ streaming : false ,
187+ prompt_tokens : 1000 ,
188+ completion_tokens : 500 ,
189+ total_tokens : 1500 ,
190+ actual_cost : 0.002 ,
191+ baseline_cost : 0.008 ,
192+ cost_savings : 0.006 ,
193+ currency : 'USD' ,
194+ baseline_model : 'premium-model' ,
195+ } ,
196+ {
197+ id : 'replay-2' ,
198+ timestamp : '2026-03-17T10:05:00Z' ,
199+ request_id : 'req-2' ,
200+ decision : 'business-route' ,
201+ original_model : 'test-model' ,
202+ selected_model : 'test-model' ,
203+ reasoning_mode : 'on' ,
204+ selection_method : 'static' ,
205+ signals : {
206+ domain : [ 'business' ] ,
207+ preference : [ 'low-latency' ] ,
208+ } ,
209+ response_status : 200 ,
210+ from_cache : true ,
211+ streaming : true ,
212+ } ,
213+ ] ,
214+ } ;
215+
216+ const replayAggregateResponse = {
217+ object : 'router_replay.aggregate' ,
218+ record_count : 2 ,
219+ summary : {
220+ total_saved : 0.006 ,
221+ baseline_spend : 0.008 ,
222+ actual_spend : 0.002 ,
223+ currency : 'USD' ,
224+ cost_record_count : 1 ,
225+ excluded_record_count : 1 ,
226+ } ,
227+ model_selection : [
228+ { name : 'test-model' , value : 2 } ,
229+ ] ,
230+ decision_distribution : [
231+ { name : 'business-route' , value : 2 } ,
232+ ] ,
233+ signal_distribution : [
234+ { name : 'domain' , value : 2 } ,
235+ { name : 'keyword' , value : 1 } ,
236+ { name : 'preference' , value : 1 } ,
237+ ] ,
238+ token_volume : {
239+ input_tokens : 1000 ,
240+ output_tokens : 500 ,
241+ total_tokens : 1500 ,
242+ excluded_record_count : 1 ,
243+ } ,
244+ token_breakdown : {
245+ by_decision : [
246+ {
247+ name : 'business-route' ,
248+ input_tokens : 1000 ,
249+ output_tokens : 500 ,
250+ total_tokens : 1500 ,
251+ } ,
252+ ] ,
253+ by_selected_model : [
254+ {
255+ name : 'test-model' ,
256+ input_tokens : 1000 ,
257+ output_tokens : 500 ,
258+ total_tokens : 1500 ,
259+ } ,
260+ ] ,
261+ } ,
262+ available_decisions : [ 'business-route' ] ,
263+ available_models : [ 'test-model' ] ,
264+ } ;
265+
115266async function mockCommon (
116267 page : Page ,
117268 options : {
@@ -173,10 +324,46 @@ async function mockCommon(
173324 body : JSON . stringify ( { canRegister : false } ) ,
174325 } ) ;
175326 } ) ;
327+
328+ await page . route ( / \/ a p i \/ r o u t e r \/ v 1 \/ r o u t e r _ r e p l a y (?: \? .* ) ? $ / , async route => {
329+ await route . fulfill ( {
330+ status : 200 ,
331+ contentType : 'application/json' ,
332+ body : JSON . stringify ( replayRecordsResponse ) ,
333+ } ) ;
334+ } ) ;
335+
336+ await page . route ( / \/ a p i \/ r o u t e r \/ v 1 \/ r o u t e r _ r e p l a y \/ a g g r e g a t e (?: \? .* ) ? $ / , async route => {
337+ await route . fulfill ( {
338+ status : 200 ,
339+ contentType : 'application/json' ,
340+ body : JSON . stringify ( replayAggregateResponse ) ,
341+ } ) ;
342+ } ) ;
343+
344+ await page . route ( '**/api/router/v1/router_replay/*' , async route => {
345+ const requestURL = new URL ( route . request ( ) . url ( ) ) ;
346+ const replayID = requestURL . pathname . split ( '/' ) . pop ( ) ;
347+ if ( replayID === 'aggregate' ) {
348+ await route . fulfill ( {
349+ status : 200 ,
350+ contentType : 'application/json' ,
351+ body : JSON . stringify ( replayAggregateResponse ) ,
352+ } ) ;
353+ return ;
354+ }
355+ const record = replayRecordsResponse . data . find ( item => item . id === replayID ) ;
356+
357+ await route . fulfill ( {
358+ status : record ? 200 : 404 ,
359+ contentType : 'application/json' ,
360+ body : JSON . stringify ( record ?? { error : { message : 'not found' } } ) ,
361+ } ) ;
362+ } ) ;
176363}
177364
178365test . describe ( 'Layout top navigation' , ( ) => {
179- test ( 'moves ClawOS into the secondary group and merges analysis and operations ' , async ( { page } ) => {
366+ test ( 'keeps Insight in primary nav and moves Manager beside System on the right ' , async ( { page } ) => {
180367 await page . setViewportSize ( { width : 1440 , height : 900 } ) ;
181368 await mockCommon ( page ) ;
182369
@@ -190,13 +377,18 @@ test.describe('Layout top navigation', () => {
190377 await expect ( primaryGroup . getByRole ( 'link' , { name : 'Playground' } ) ) . toBeVisible ( ) ;
191378 await expect ( primaryGroup . getByRole ( 'link' , { name : 'Brain' } ) ) . toBeVisible ( ) ;
192379 await expect ( primaryGroup . getByRole ( 'link' , { name : 'DSL' } ) ) . toBeVisible ( ) ;
193- await expect ( primaryGroup . getByRole ( 'button' , { name : 'Manager' } ) ) . toBeVisible ( ) ;
380+ await expect ( primaryGroup . getByRole ( 'link' , { name : 'Insight' } ) ) . toBeVisible ( ) ;
381+ await expect ( primaryGroup . getByRole ( 'button' , { name : 'Manager' } ) ) . toHaveCount ( 0 ) ;
194382 await expect ( primaryGroup . getByRole ( 'link' , { name : 'ClawOS' } ) ) . toHaveCount ( 0 ) ;
195383 await expect ( primaryGroup . getByRole ( 'link' , { name : 'Users' } ) ) . toHaveCount ( 0 ) ;
196384
197385 await expect ( secondaryGroup . getByRole ( 'link' , { name : 'Users' } ) ) . toBeVisible ( ) ;
198386 await expect ( secondaryGroup . getByRole ( 'link' , { name : 'ClawOS' } ) ) . toBeVisible ( ) ;
387+ await expect ( secondaryGroup . getByRole ( 'button' , { name : 'Manager' } ) ) . toBeVisible ( ) ;
199388 await expect ( secondaryGroup . getByRole ( 'button' , { name : 'System' } ) ) . toBeVisible ( ) ;
389+ const secondaryButtons = secondaryGroup . getByRole ( 'button' ) ;
390+ await expect ( secondaryButtons . nth ( 0 ) ) . toHaveText ( / M a n a g e r / ) ;
391+ await expect ( secondaryButtons . nth ( 1 ) ) . toHaveText ( / S y s t e m / ) ;
200392 await expect ( globalNav . getByRole ( 'button' , { name : 'Analysis' , exact : true } ) ) . toHaveCount ( 0 ) ;
201393 await expect ( globalNav . getByRole ( 'button' , { name : 'Operations' , exact : true } ) ) . toHaveCount ( 0 ) ;
202394
@@ -207,9 +399,10 @@ test.describe('Layout top navigation', () => {
207399 const menuItems = menu . getByRole ( 'menuitem' ) ;
208400 await expect ( menuItems . nth ( 0 ) ) . toHaveText ( 'Global Config' ) ;
209401 await expect ( menuItems . nth ( 1 ) ) . toHaveText ( 'Evaluation' ) ;
402+ await expect ( menuItems . nth ( 2 ) ) . toHaveText ( 'Ratings' ) ;
210403 await expect ( menu . getByRole ( 'menuitem' , { name : 'Global Config' } ) ) . toBeVisible ( ) ;
211404 await expect ( menu . getByRole ( 'menuitem' , { name : 'Evaluation' } ) ) . toBeVisible ( ) ;
212- await expect ( menu . getByRole ( 'menuitem' , { name : 'Replay' } ) ) . toBeVisible ( ) ;
405+ await expect ( menu . getByRole ( 'menuitem' , { name : 'Replay' } ) ) . toHaveCount ( 0 ) ;
213406 await expect ( menu . getByRole ( 'menuitem' , { name : 'Ratings' } ) ) . toBeVisible ( ) ;
214407 await expect ( menu . getByText ( 'Operations' ) ) . toBeVisible ( ) ;
215408 await expect ( menu . getByRole ( 'menuitem' , { name : 'ML Setup' } ) ) . toBeVisible ( ) ;
@@ -220,6 +413,65 @@ test.describe('Layout top navigation', () => {
220413 await expect ( menu . getByRole ( 'menuitem' , { name : 'Tracing' } ) ) . toBeVisible ( ) ;
221414 } ) ;
222415
416+ test ( 'loads Insights charts and does not preserve the legacy replay route' , async ( { page } ) => {
417+ await page . setViewportSize ( { width : 1440 , height : 900 } ) ;
418+ await mockCommon ( page ) ;
419+
420+ await page . goto ( '/dashboard' ) ;
421+ await page . getByRole ( 'link' , { name : 'Insight' } ) . click ( ) ;
422+
423+ await expect ( page ) . toHaveURL ( / \/ i n s i g h t s $ / ) ;
424+ await expect ( page . getByRole ( 'heading' , { name : 'Insights' , exact : true } ) ) . toBeVisible ( ) ;
425+ await expect ( page . getByText ( 'Total Saved' ) ) . toBeVisible ( ) ;
426+ await expect ( page . getByText ( 'Saved %' ) ) . toBeVisible ( ) ;
427+ await expect ( page . getByText ( 'Baseline Spend' ) ) . toBeVisible ( ) ;
428+ await expect ( page . getByText ( 'Actual Spend' ) ) . toBeVisible ( ) ;
429+ await expect ( page . getByText ( 'See what the router picked, what signals fired, and how much it saved.' ) ) . toBeVisible ( ) ;
430+ await expect ( page . getByText ( 'Decisions, model picks, token usage, and savings in one view.' ) ) . toBeVisible ( ) ;
431+ await expect ( page . getByText ( 'Replay-backed routing records with spend, savings, and token details per request.' ) ) . toBeVisible ( ) ;
432+ await expect ( page . getByRole ( 'heading' , { name : 'Model Selection' , exact : true } ) ) . toBeVisible ( ) ;
433+ await expect ( page . getByRole ( 'heading' , { name : 'Decision Distribution' , exact : true } ) ) . toBeVisible ( ) ;
434+ await expect ( page . getByRole ( 'heading' , { name : 'Signal Distribution' , exact : true } ) ) . toBeVisible ( ) ;
435+ await expect ( page . getByRole ( 'heading' , { name : 'Token Volume' , exact : true } ) ) . toBeVisible ( ) ;
436+ await expect ( page . getByRole ( 'heading' , { name : 'Tokens by Decision' , exact : true } ) ) . toBeVisible ( ) ;
437+ await expect ( page . getByRole ( 'heading' , { name : 'Tokens by Selected Model' , exact : true } ) ) . toBeVisible ( ) ;
438+ await expect ( page . getByText ( 'Input Tokens' ) . first ( ) ) . toBeVisible ( ) ;
439+ await expect ( page . getByText ( 'Output Tokens' ) . first ( ) ) . toBeVisible ( ) ;
440+ await expect ( page . getByText ( 'Total Tokens' ) . first ( ) ) . toBeVisible ( ) ;
441+ await expect ( page . getByText ( '1 filtered record excluded from cost totals because usage or pricing data is incomplete.' ) ) . toBeVisible ( ) ;
442+ await expect ( page . getByText ( '1 filtered record excluded from token totals because usage data is incomplete.' ) ) . toBeVisible ( ) ;
443+ await expect (
444+ page . getByRole ( 'article' ) . filter ( { hasText : 'Total Saved' } ) . getByRole ( 'strong' ) ,
445+ ) . toHaveText ( '$0.0060' ) ;
446+ await expect (
447+ page . getByRole ( 'article' ) . filter ( { hasText : 'Saved %' } ) . getByRole ( 'strong' ) ,
448+ ) . toHaveText ( '75.0%' ) ;
449+ await expect ( page . getByRole ( 'columnheader' , { name : 'Actual Cost' } ) ) . toBeVisible ( ) ;
450+ await expect ( page . getByRole ( 'columnheader' , { name : 'Saved vs Baseline' } ) ) . toBeVisible ( ) ;
451+
452+ await page . goto ( '/replay' ) ;
453+ await expect ( page ) . toHaveURL ( / \/ d a s h b o a r d $ / ) ;
454+ } ) ;
455+
456+ test ( 'renders nested canonical decision groups in Brain topology' , async ( { page } ) => {
457+ await page . setViewportSize ( { width : 1440 , height : 900 } ) ;
458+ await mockCommon ( page ) ;
459+
460+ await page . goto ( '/topology' ) ;
461+
462+ const decisionNode = page
463+ . locator ( '[class*="decisionNode"]' )
464+ . filter ( { has : page . getByText ( 'medium_business' , { exact : true } ) } )
465+ . first ( ) ;
466+
467+ await expect ( decisionNode ) . toBeVisible ( ) ;
468+ await expect ( decisionNode ) . toContainText ( 'AND' ) ;
469+ await expect ( decisionNode ) . toContainText ( 'OR: domain: business | domain: economics' ) ;
470+ await expect ( decisionNode ) . toContainText ( 'OR: embedding: business_analysis' ) ;
471+ await expect ( decisionNode ) . toContainText ( 'preference: structured_delivery' ) ;
472+ await expect ( decisionNode . getByText ( 'Referenced signals not configured' ) ) . toHaveCount ( 0 ) ;
473+ } ) ;
474+
223475 test ( 'hides ML Setup for read users and redirects direct access back to the dashboard' , async ( { page } ) => {
224476 await page . setViewportSize ( { width : 1440 , height : 900 } ) ;
225477 await mockCommon ( page , { user : readUser } ) ;
0 commit comments