11import test from 'node:test' ;
22import assert from 'node:assert/strict' ;
3- import { readFileSync } from 'node:fs' ;
3+ import { readFileSync , readdirSync } from 'node:fs' ;
44import path from 'node:path' ;
55import { fileURLToPath } from 'node:url' ;
66
@@ -9,6 +9,7 @@ import { fileURLToPath } from 'node:url';
99// insets and gap, a 29px identity row, and a 32px trailing utility button.
1010
1111const rendererDir = path . dirname ( fileURLToPath ( import . meta. url ) ) ;
12+ const stylesDir = path . join ( rendererDir , 'styles' ) ;
1213const read = ( relativePath ) =>
1314 readFileSync ( path . join ( rendererDir , relativePath ) , 'utf8' ) ;
1415
@@ -57,13 +58,13 @@ test('sidebar footer pins the Codex frame and divider geometry', () => {
5758} ) ;
5859
5960test ( 'gateway and settings controls pin the Codex footer alignment' , ( ) => {
60- const layoutCss = read ( 'styles/workflows .css' ) ;
61- expectRule ( layoutCss , '.gateway-identity-bar' , [
61+ const ownerCss = read ( 'styles/gateway-status .css' ) ;
62+ expectRule ( ownerCss , '.gateway-identity-bar' , [
6263 'gap: 8px' ,
6364 'height: 46px' ,
6465 'padding: 0 8px' ,
6566 ] ) ;
66- expectRule ( layoutCss , '.gateway-identity-main' , [
67+ expectRule ( ownerCss , '.gateway-identity-main' , [
6768 'gap: 8px' ,
6869 'height: 29px' ,
6970 'padding: 0 8px' ,
@@ -73,17 +74,16 @@ test('gateway and settings controls pin the Codex footer alignment', () => {
7374 'font-weight: 445' ,
7475 'line-height: 21px' ,
7576 ] ) ;
76- expectRule ( layoutCss , '.gateway-identity-main:hover' , [
77+ expectRule ( ownerCss , '.gateway-identity-main:hover' , [
7778 'background: var(--color-token-row-hover)' ,
7879 ] ) ;
7980
80- const controlsCss = read ( 'styles/task-forest.css' ) ;
81- expectRule ( controlsCss , '.gateway-identity-name' , [
81+ expectRule ( ownerCss , '.gateway-identity-name' , [
8282 'font-size: inherit' ,
8383 'font-weight: inherit' ,
8484 'line-height: inherit' ,
8585 ] ) ;
86- expectRule ( controlsCss , '.gateway-identity-gear' , [
86+ expectRule ( ownerCss , '.gateway-identity-gear' , [
8787 'gap: 4px' ,
8888 'width: 32px' ,
8989 'height: 32px' ,
@@ -93,7 +93,7 @@ test('gateway and settings controls pin the Codex footer alignment', () => {
9393 'color: var(--color-token-description-foreground)' ,
9494 'font: 445 16px/24px -apple-system' ,
9595 ] ) ;
96- expectRule ( controlsCss , '.gateway-identity-gear:hover' , [
96+ expectRule ( ownerCss , '.gateway-identity-gear:hover' , [
9797 'background: var(--color-token-row-hover)' ,
9898 ] ) ;
9999} ) ;
@@ -122,3 +122,26 @@ test('gateway identity keeps Codex icon scale while preserving status semantics'
122122 ) ;
123123 assert . ok ( ! source . includes ( 'gateway-identity-status' ) ) ;
124124} ) ;
125+
126+ test ( 'gateway switcher recipe is always loaded and has one stylesheet owner' , ( ) => {
127+ const entryCss = read ( 'styles.css' ) ;
128+ assert . equal (
129+ entryCss . match ( / @ i m p o r t " \. \/ s t y l e s \/ g a t e w a y - s t a t u s \. c s s " ; / g) ?. length ,
130+ 1 ,
131+ 'the renderer entrypoint must import the gateway owner stylesheet exactly once' ,
132+ ) ;
133+ assert . ok (
134+ ! entryCss . includes ( 'workflows.css' ) ,
135+ 'app-shell gateway chrome must not depend on the removed workflow feature' ,
136+ ) ;
137+
138+ const gatewaySelector = / \. (?: g a t e w a y - i d e n t i t y | g a t e w a y - s w i t c h e r | g a t e w a y - r o w - g l y p h | g a t e w a y - g l y p h - b a d g e ) (?: [ - \w ] * ) / ;
139+ const offenders = readdirSync ( stylesDir )
140+ . filter ( ( fileName ) => fileName . endsWith ( '.css' ) && fileName !== 'gateway-status.css' )
141+ . filter ( ( fileName ) => gatewaySelector . test ( read ( `styles/${ fileName } ` ) ) ) ;
142+ assert . deepEqual (
143+ offenders ,
144+ [ ] ,
145+ `gateway switcher selectors escaped their owner stylesheet: ${ offenders . join ( ', ' ) } ` ,
146+ ) ;
147+ } ) ;
0 commit comments