/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import { strict as assert } from 'node:assert'; import { spawnSync } from 'node:child_process'; import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { dirname, join } from 'node:path'; import { describe, it } from 'node:test'; import { fileURLToPath } from 'node:url'; import { analyzeRendererSource, checkRendererArchitecture, generateArchitectureConfig, } from './check-renderer-architecture.mjs'; import { assertRendererEntryHtml, rendererEntryContractPlugin, } from './vite-renderer-entry-contract.ts'; const TRANSITIVE_APP_SHELL_PATH = 'src/renderer/app-shell.ts'; const TRANSITIVE_LEGACY_HELPER_PATH = 'src/renderer/legacy-session-helper.ts'; const RENDERER_ENTRY_PATH = 'src/renderer/main.tsx'; function emptyDebt(overrides = {}) { return { importDeclarations: 0, importSpecifiers: 0, nonTriviaTokens: 0, dependencyPaths: {}, bridgePaths: {}, environmentCapabilities: {}, hookCalls: {}, lifecycleMethods: {}, unresolvedDependencies: 0, actionFactories: [], ...overrides, }; } function architectureConfig({ legacyAppShellClosureDebt, legacyFeatureImports = [], legacyFiles = {}, legacyGrowthDirectories = [], legacyPlatformImports = [], rootDebt = {}, rootDebtClosure = {}, legacyRendererFiles = Object.keys(rootDebt), ownership = [], } = {}) { return { version: 1, legacyRendererFiles: [...legacyRendererFiles].sort(), legacyGrowthDirectories: [...legacyGrowthDirectories].sort(), legacyFeatureImports: [...legacyFeatureImports].sort(), legacyPlatformImports: [...legacyPlatformImports].sort(), legacyAppShell: { files: legacyFiles, closure: legacyAppShellClosureDebt ?? {}, }, rootDebt, rootDebtClosure, ownership, }; } function debtForSource(source, path) { const analysis = analyzeRendererSource(source, path); return { importDeclarations: analysis.importDeclarations, importSpecifiers: analysis.importSpecifiers, nonTriviaTokens: analysis.nonTriviaTokens, dependencyPaths: analysis.dependencyPaths, bridgePaths: analysis.bridgePaths, environmentCapabilities: analysis.environmentCapabilities, hookCalls: analysis.hookCalls, lifecycleMethods: analysis.lifecycleMethods, unresolvedDependencies: analysis.unresolvedDependencies, actionFactories: analysis.actionFactories, }; } function capabilityDebtForSource(source, path) { const debt = debtForSource(source, path); return { actionFactories: debt.actionFactories, bridgePaths: debt.bridgePaths, dependencyPaths: debt.dependencyPaths, environmentCapabilities: debt.environmentCapabilities, hookCalls: debt.hookCalls, lifecycleMethods: debt.lifecycleMethods, unresolvedDependencies: debt.unresolvedDependencies, }; } async function withDesktopFixture(files, run) { const desktopRoot = await mkdtemp(join(tmpdir(), 'maka-renderer-architecture-')); try { for (const [path, source] of Object.entries(files)) { const absolutePath = join(desktopRoot, path); await mkdir(dirname(absolutePath), { recursive: true }); await writeFile(absolutePath, source, 'utf8'); } return await run(desktopRoot); } finally { await rm(desktopRoot, { force: true, recursive: true }); } } function violationsFor(desktopRoot, config = architectureConfig(), baseConfig) { return checkRendererArchitecture({ baseConfig, config, desktopRoot, enforceRendererEntryContract: false, }); } function assertHasViolation(violations, pattern) { assert.ok( violations.some((violation) => pattern.test(violation)), `Expected a violation matching ${pattern}, received:\n${violations.join('\n')}`, ); } function transitiveAppShellFiles(helperSource, extraFiles = {}) { return { [TRANSITIVE_APP_SHELL_PATH]: ` import { legacySessionHelper } from './legacy-session-helper.js'; export const AppShell = legacySessionHelper; `, [TRANSITIVE_LEGACY_HELPER_PATH]: helperSource, ...extraFiles, }; } function transitiveAppShellSeedConfig() { return architectureConfig({ ownership: [ { capability: 'fixture-app-shell', targetZone: 'shell', legacyPaths: [TRANSITIVE_APP_SHELL_PATH], }, ], }); } function rendererEntrySeedConfig() { return architectureConfig({ rootDebt: { [RENDERER_ENTRY_PATH]: emptyDebt() }, ownership: [ { capability: 'fixture-root', targetZone: 'bootstrap', legacyPaths: [RENDERER_ENTRY_PATH], }, ], }); } function rendererEntryContractFiles(overrides = {}) { return { 'src/renderer/index.html': ` `, [RENDERER_ENTRY_PATH]: `export const main = true;`, 'src/main/main-window.ts': ` import { loadMainRenderer, resolveMainRendererEntry } from './main-renderer-loader.js'; async function createWindow() { const rendererEntry = resolveMainRendererEntry(import.meta.dirname, process.env.VITE_DEV_SERVER_URL); await loadMainRenderer(mainWindow, rendererEntry); } `, 'src/main/main-renderer-loader.ts': ` import { join } from 'node:path'; import { pathToFileURL } from 'node:url'; interface MainRendererWindow { loadFile(path: string): Promise; loadURL(url: string): Promise; } interface MainRendererEntry { readonly filePath: string; readonly url: string; readonly useDevServer: boolean; } export function resolveMainRendererEntry( mainModuleDirectory: string, viteDevServerUrl: string | undefined, ): MainRendererEntry { const rendererEntryPath = join(mainModuleDirectory, '..', '..', 'dist-renderer', 'index.html'); const rendererEntryUrl = viteDevServerUrl ?? pathToFileURL(rendererEntryPath).href; return Object.freeze({ filePath: rendererEntryPath, url: rendererEntryUrl, useDevServer: !!viteDevServerUrl, }); } export async function loadMainRenderer( mainWindow: MainRendererWindow, rendererEntry: MainRendererEntry, ): Promise { if (rendererEntry.useDevServer) { await mainWindow.loadURL(rendererEntry.url); } else { await mainWindow.loadFile(rendererEntry.filePath); } } `, 'vite.config.ts': ` import react from '@vitejs/plugin-react'; import { resolve } from 'node:path'; import { defineConfig } from 'vite'; import { rendererEntryContractPlugin } from './scripts/vite-renderer-entry-contract.js'; import { bundledNpmPackagesPlugin } from './vite-bundled-packages.js'; import { dependencyPatchesCachePlugin } from './vite-dependency-patches.js'; const REPO_ROOT = '/fixture'; export default defineConfig({ root: 'src/renderer', plugins: [ react(), dependencyPatchesCachePlugin(REPO_ROOT), bundledNpmPackagesPlugin(), rendererEntryContractPlugin(resolve(import.meta.dirname, 'src/renderer')), ], build: { outDir: '../../dist-renderer' }, }); `, 'package.json': JSON.stringify({ scripts: { 'build:renderer': 'vite build && node scripts/check-renderer-entry-output.mjs && node ../../scripts/check-third-party-notices.mjs', }, }), ...overrides, }; } function runRendererEntryBundleContract(directImports) { const plugin = rendererEntryContractPlugin('/fixture/src/renderer'); plugin.configResolved({ root: '/fixture/src/renderer' }); assert.equal(typeof plugin.generateBundle, 'function'); plugin.generateBundle.call( { emitFile() {}, error(message) { throw new Error(message); }, getModuleInfo(id) { return id === '/fixture/src/renderer/index.html' ? { importedIds: directImports } : null; }, }, {}, { 'assets/index.js': { type: 'chunk', isEntry: true, facadeModuleId: '/fixture/src/renderer/index.html', }, }, ); } function canonicalRendererEntryHtml(extraBody = '', policy = "script-src 'self'") { return ` Maka
${extraBody} `; } describe('renderer architecture checker fixtures', () => { it('accepts the legal inward dependency graph and testing entry boundary', async () => { await withDesktopFixture( { 'src/renderer/application/contracts/regions.ts': ` export interface RegionModel { readonly id: string } export const fetch = () => 'injected-contract'; `, 'src/renderer/application/session/session-scope.ts': ` import type { RegionModel } from '../contracts/regions.js'; export type SessionScope = RegionModel; `, 'src/renderer/shell/shell-frame.tsx': ` import type { RegionModel } from '../application/contracts/regions.js'; import { fetch } from '../application/contracts/regions.js'; export function ShellFrame(props: { readonly region: RegionModel }) { return
; } `, 'src/renderer/features/alpha/index.ts': ` export { AlphaHost } from './ui/alpha-host.js'; export type { AlphaServices } from './ports.js'; `, 'src/renderer/features/alpha/ports.ts': ` export interface AlphaServices { readonly read: () => Promise } `, 'src/renderer/features/alpha/testing.ts': ` export function createFakeAlphaServices() { return { read: async () => 'fixture' }; } `, 'src/renderer/features/alpha/stories.ts': ` export const alphaStoryModel = { id: 'alpha-story' }; `, 'src/renderer/features/alpha/ui/alpha-host.tsx': ` import type { RegionModel } from '../../../application/contracts/regions.js'; export function AlphaHost(props: RegionModel) { return
{props.id}
; } `, 'src/renderer/platform/desktop/create-alpha-services.ts': ` import type { AlphaServices } from '../../features/alpha/index.js'; export function createAlphaServices(): AlphaServices { return { read: async () => 'desktop' }; } `, 'src/renderer/composition/desktop-application.tsx': ` import { AlphaHost } from '../features/alpha/index.js'; import { createAlphaServices } from '../platform/desktop/create-alpha-services.js'; import { ShellFrame } from '../shell/shell-frame.js'; void createAlphaServices; export function DesktopApplication() { return <>; } `, 'src/main/__tests__/alpha-boundary.test.ts': ` import { createFakeAlphaServices } from '../../renderer/features/alpha/testing.js'; void createFakeAlphaServices; `, 'stories/alpha.stories.tsx': ` import { createFakeAlphaServices } from '../src/renderer/features/alpha/testing.js'; import { alphaStoryModel } from '../src/renderer/features/alpha/stories.js'; export const services = createFakeAlphaServices(); export const model = alphaStoryModel; `, }, (desktopRoot) => { assert.deepEqual(violationsFor(desktopRoot), []); }, ); }); it('resolves @maka/desktop self-imports before enforcing zone boundaries', async () => { await withDesktopFixture( { 'src/renderer/features/alpha/controller.ts': ` import { desktopOnly } from '@maka/desktop/src/renderer/platform/desktop/private.js'; export const alpha = desktopOnly; `, 'src/renderer/shell/shell-frame.ts': ` import { BetaHost } from '@maka/desktop/src/renderer/features/beta/index.js'; export const frame = BetaHost; `, 'src/renderer/application/session/session-scope.ts': ` import { BetaHost } from '@maka/desktop/src/renderer/features/beta/index.js'; export const scope = BetaHost; `, 'src/renderer/features/beta/index.ts': 'export const BetaHost = true;', 'src/renderer/platform/desktop/private.ts': 'export const desktopOnly = true;', }, (desktopRoot) => { const violations = violationsFor(desktopRoot); assertHasViolation( violations, /features\/alpha\/controller\.ts: feature imports a forbidden Desktop\/shell module/u, ); assertHasViolation( violations, /shell\/shell-frame\.ts: shell imports forbidden implementation module/u, ); assertHasViolation( violations, /application\/session\/session-scope\.ts: application authority imports an outer implementation/u, ); }, ); }); it('rejects direct browser environment access in shell and composition files', async () => { await withDesktopFixture( { 'src/renderer/shell/local-storage.ts': ` export const persisted = localStorage.getItem('shell-layout'); `, 'src/renderer/shell/window-listener.ts': ` window.addEventListener('resize', () => undefined); `, 'src/renderer/composition/fetch.ts': ` export const request = fetch('/renderer-bootstrap'); `, 'src/renderer/composition/timers.ts': ` setTimeout(() => undefined, 1); requestAnimationFrame(() => undefined); `, 'src/renderer/shell/aliased-environment.ts': ` const later = setTimeout; const retrieve = fetch; later(() => undefined, 1); void retrieve('/shell-data'); `, 'src/renderer/composition/browser-globals.ts': ` void indexedDB.open('maka'); history.pushState({}, '', '/'); void new FileReader(); addEventListener('online', () => undefined); `, }, (desktopRoot) => { const violations = violationsFor(desktopRoot); for (const path of [ 'src/renderer/shell/local-storage.ts', 'src/renderer/shell/window-listener.ts', 'src/renderer/composition/fetch.ts', 'src/renderer/composition/timers.ts', 'src/renderer/shell/aliased-environment.ts', 'src/renderer/composition/browser-globals.ts', ]) { assertHasViolation( violations, new RegExp( `^${path.replaceAll('/', '\\/').replaceAll('.', '\\.')}.*directly accesses browser environment capabilities$`, 'u', ), ); } }, ); }); it('resolves browser-global shadowing lexically instead of masking the whole file', () => { const analysis = analyzeRendererSource( ` function useInjected(fetch, history, setTimeout) { fetch('/injected'); history.pushState({}, '', '/injected'); setTimeout(() => undefined, 1); } void useInjected; fetch('/global'); history.pushState({}, '', '/global'); setTimeout(() => undefined, 1); `, 'src/renderer/shell/lexical-browser-globals.ts', ); assert.equal(analysis.environmentCapabilities.fetch, 1); assert.equal(analysis.environmentCapabilities['history.pushState'], 1); assert.equal(analysis.environmentCapabilities.setTimeout, 1); }); it('rejects computed and optional access to the Desktop bridge in strict zones', async () => { await withDesktopFixture( { 'src/renderer/application/computed-bridge.ts': ` void window['maka'].sessions.list(); `, 'src/renderer/application/optional-bridge.ts': ` void window?.maka?.sessions?.subscribeEvents?.(() => undefined); `, 'src/renderer/application/dynamic-global.ts': ` const bridgeName = 'maka'; void window[bridgeName]; `, }, (desktopRoot) => { const violations = violationsFor(desktopRoot); assertHasViolation( violations, /^src\/renderer\/application\/computed-bridge\.ts: application code accesses the Desktop global bridge$/u, ); assertHasViolation( violations, /^src\/renderer\/application\/optional-bridge\.ts: application code accesses the Desktop global bridge$/u, ); assertHasViolation( violations, /^src\/renderer\/application\/dynamic-global\.ts: application code contains non-static global environment access$/u, ); }, ); }); it('rejects cross-feature, deep feature, and production testing imports', async () => { await withDesktopFixture( { 'src/renderer/features/alpha/controller.ts': ` export async function loadBeta() { return import('../beta/index.js'); } `, 'src/renderer/features/beta/index.ts': ` export const beta = true; `, 'src/renderer/composition/deep-feature.ts': ` const alphaController = require('../features/alpha/controller/use-alpha.js'); void alphaController; `, 'src/renderer/composition/testing-in-production.ts': ` import { createFakeAlphaServices } from '../features/alpha/testing.js'; void createFakeAlphaServices; `, }, (desktopRoot) => { const violations = violationsFor(desktopRoot); assertHasViolation( violations, /^src\/renderer\/features\/alpha\/controller\.ts: feature alpha imports feature beta:/u, ); assertHasViolation( violations, /^src\/renderer\/composition\/deep-feature\.ts: feature imports must use index:/u, ); assertHasViolation( violations, /^src\/renderer\/composition\/testing-in-production\.ts: production code imports a feature testing entry:/u, ); }, ); }); it('rejects a feature production module importing its own testing entry', async () => { await withDesktopFixture( { 'src/renderer/features/alpha/controller.ts': ` import { fakeAlpha } from './testing.js'; export const alpha = fakeAlpha; `, 'src/renderer/features/alpha/testing.ts': 'export const fakeAlpha = true;', }, (desktopRoot) => { assertHasViolation( violationsFor(desktopRoot), /features\/alpha\/controller\.ts:.*testing entry/u, ); }, ); }); it('keeps feature stories entries test-only', async () => { await withDesktopFixture( { 'src/renderer/features/alpha/stories.ts': 'export const storyModel = true;', 'src/renderer/composition/stories-in-production.ts': ` import { storyModel } from '../features/alpha/stories.js'; export const leakedStoryModel = storyModel; `, }, (desktopRoot) => { assertHasViolation( violationsFor(desktopRoot), /composition\/stories-in-production\.ts: production code imports a feature stories entry/u, ); }, ); }); it('rejects an application contract re-exporting application implementation', async () => { await withDesktopFixture( { 'src/renderer/application/contracts/index.ts': ` export { sessionStore } from '../session/session-store.js'; `, 'src/renderer/application/session/session-store.ts': ` export const sessionStore = true; `, }, (desktopRoot) => { assertHasViolation( violationsFor(desktopRoot), /application\/contracts\/index\.ts:.*(?:contract|contracts).*implementation/iu, ); }, ); }); it('rejects composition and Desktop adapters importing deep application implementation', async () => { await withDesktopFixture( { 'src/renderer/application/session/session-store.ts': `export const sessionStore = true;`, 'src/renderer/composition/deep-application.ts': ` import { sessionStore } from '../application/session/session-store.js'; export const composed = sessionStore; `, 'src/renderer/platform/desktop/deep-application.ts': ` import { sessionStore } from '../../application/session/session-store.js'; export const adapted = sessionStore; `, }, (desktopRoot) => { const violations = violationsFor(desktopRoot); assertHasViolation( violations, /composition\/deep-application\.ts: composition imports application implementation instead of a public entry/u, ); assertHasViolation( violations, /platform\/desktop\/deep-application\.ts: Desktop adapter imports application implementation instead of a public entry/u, ); }, ); }); it('rejects Electron and node built-in imports from strict feature code', async () => { await withDesktopFixture( { 'src/renderer/features/alpha/electron-import.ts': ` import { ipcRenderer } from 'electron'; export const ipc = ipcRenderer; `, 'src/renderer/features/alpha/node-import.ts': ` import { readFile } from 'node:fs/promises'; export const read = readFile; `, 'src/renderer/features/alpha/controller.test.ts': ` import { strict as assert } from 'node:assert'; assert.equal(1, 1); `, }, (desktopRoot) => { const violations = violationsFor(desktopRoot); assertHasViolation( violations, /features\/alpha\/electron-import\.ts:.*feature.*electron/iu, ); assertHasViolation( violations, /features\/alpha\/node-import\.ts:.*feature.*node:fs/iu, ); assert.ok( !violations.some((violation) => violation.includes('controller.test.ts')), `Feature tests may use the Node test environment:\n${violations.join('\n')}`, ); }, ); }); it('keeps Desktop platform adapters free of UI lifecycle and runtime module loading', async () => { await withDesktopFixture( { 'src/renderer/platform/desktop/stateful-view.tsx': ` import * as React from 'react'; import { useState } from 'react'; export function StatefulView() { const [open] = useState(false); return
{String(open)}
; } export class StatefulAdapter extends React.Component { componentDidMount() {} render() { return null; } } `, 'src/renderer/platform/desktop/dynamic-adapter.ts': ` export function loadAdapter(path: string) { return import(path); } `, 'src/renderer/platform/desktop/electron-adapter.ts': ` import { ipcRenderer } from 'electron'; export const ipc = ipcRenderer; `, 'src/renderer/platform/desktop/node-adapter.ts': ` import { readFile } from 'node:fs/promises'; export const read = readFile; `, 'src/renderer/platform/desktop/allowed-capabilities.ts': ` export function createAllowedCapabilities() { localStorage.getItem('desktop-adapter'); return window.maka.sessions.list(); } `, }, (desktopRoot) => { const violations = violationsFor(desktopRoot); assertHasViolation( violations, /platform\/desktop\/stateful-view\.tsx: platform code owns stateful React hooks/u, ); assertHasViolation( violations, /platform\/desktop\/stateful-view\.tsx: platform code owns React class lifecycle methods/u, ); assertHasViolation( violations, /platform\/desktop\/stateful-view\.tsx: Desktop adapters cannot own React UI/u, ); assertHasViolation( violations, /platform\/desktop\/dynamic-adapter\.ts: platform code contains a non-static import or require/u, ); assertHasViolation( violations, /platform\/desktop\/electron-adapter\.ts: platform code imports forbidden environment module: electron/u, ); assertHasViolation( violations, /platform\/desktop\/node-adapter\.ts: platform code imports forbidden environment module: node:fs\/promises/u, ); assert.ok( !violations.some((violation) => violation.includes('allowed-capabilities.ts')), `Desktop adapters may own the bridge and browser environment:\n${violations.join('\n')}`, ); }, ); }); it('rejects new AppShell-family helper files outside the debt ledger', async () => { await withDesktopFixture( { 'src/renderer/app-shell-fresh-owner.ts': 'export const owner = true;', 'src/renderer/use-app-shell-fresh-owner.ts': 'export const owner = true;', }, (desktopRoot) => { const violations = violationsFor(desktopRoot); assertHasViolation( violations, /^legacy AppShell file set changed;.*app-shell-fresh-owner\.ts.*use-app-shell-fresh-owner\.ts/u, ); }, ); }); it('generates the transitive legacy closure owned directly by AppShell', async () => { const helperSource = ` export const legacySessionHelper = 'legacy-session'; `; await withDesktopFixture( transitiveAppShellFiles(helperSource), (desktopRoot) => { const generated = generateArchitectureConfig( desktopRoot, transitiveAppShellSeedConfig(), ); assert.deepEqual( Object.keys(generated.legacyAppShell.closure), [TRANSITIVE_LEGACY_HELPER_PATH], ); assert.deepEqual( generated.legacyAppShell.closure[TRANSITIVE_LEGACY_HELPER_PATH], capabilityDebtForSource(helperSource, TRANSITIVE_LEGACY_HELPER_PATH), ); }, ); }); it('prefers runtime source over a same-stem declaration in the AppShell closure', async () => { const runtimePath = 'src/renderer/legacy-session-helper.js'; const runtimeSource = ` export function legacySessionHelper() { return window.maka.sessions.list(); } `; await withDesktopFixture( { [TRANSITIVE_APP_SHELL_PATH]: ` import { legacySessionHelper } from './legacy-session-helper.js'; export const AppShell = legacySessionHelper; `, 'src/renderer/legacy-session-helper.d.ts': ` export declare function legacySessionHelper(): Promise; `, [runtimePath]: runtimeSource, }, (desktopRoot) => { const generated = generateArchitectureConfig( desktopRoot, transitiveAppShellSeedConfig(), ); assert.deepEqual(Object.keys(generated.legacyAppShell.closure), [runtimePath]); assert.deepEqual( generated.legacyAppShell.closure[runtimePath], capabilityDebtForSource(runtimeSource, runtimePath), ); }, ); }); it('ratchets legacy helpers reachable through a feature public ownership boundary', async () => { await withDesktopFixture( { [TRANSITIVE_APP_SHELL_PATH]: ` import { AlphaHost } from './features/alpha/index.js'; export const AppShell = AlphaHost; `, 'src/renderer/features/alpha/index.ts': ` import { legacySessionHelper } from '../../legacy-session-helper.js'; export const AlphaHost = legacySessionHelper; `, [TRANSITIVE_LEGACY_HELPER_PATH]: ` import { useState } from 'react'; export function legacySessionHelper() { void window.maka.sessions.list(); localStorage.getItem('feature-owned'); return useState(false); } `, }, (desktopRoot) => { const currentConfig = generateArchitectureConfig( desktopRoot, transitiveAppShellSeedConfig(), ); assert.deepEqual( Object.keys(currentConfig.legacyAppShell.closure), [TRANSITIVE_LEGACY_HELPER_PATH], ); const baseConfig = structuredClone(currentConfig); const baseHelperDebt = baseConfig.legacyAppShell.closure[TRANSITIVE_LEGACY_HELPER_PATH]; baseHelperDebt.hookCalls = {}; baseHelperDebt.bridgePaths = {}; baseHelperDebt.environmentCapabilities = {}; const violations = violationsFor(desktopRoot, currentConfig, baseConfig); assertHasViolation( violations, /^src\/renderer\/legacy-session-helper\.ts: new or increased hookCalls debt useState$/u, ); assertHasViolation( violations, /^src\/renderer\/legacy-session-helper\.ts: new or increased bridgePaths debt window\.maka\.sessions\.list$/u, ); assertHasViolation( violations, /^src\/renderer\/legacy-session-helper\.ts: new or increased environmentCapabilities debt localStorage\.getItem$/u, ); }, ); }); it('ratchets legacy helpers reachable through a shared Desktop boundary', async () => { await withDesktopFixture( { [TRANSITIVE_APP_SHELL_PATH]: ` import { rootBridge } from '../shared/root-bridge.js'; export const AppShell = rootBridge; `, 'src/shared/root-bridge.ts': ` import { legacySessionHelper } from '../renderer/legacy-session-helper.js'; export const rootBridge = legacySessionHelper; `, [TRANSITIVE_LEGACY_HELPER_PATH]: ` export function legacySessionHelper() { return window.maka.sessions.list(); } `, }, (desktopRoot) => { const currentConfig = generateArchitectureConfig( desktopRoot, transitiveAppShellSeedConfig(), ); assert.deepEqual( Object.keys(currentConfig.legacyAppShell.closure), [TRANSITIVE_LEGACY_HELPER_PATH, 'src/shared/root-bridge.ts'], ); const baseConfig = structuredClone(currentConfig); baseConfig.legacyAppShell.closure[TRANSITIVE_LEGACY_HELPER_PATH].bridgePaths = {}; assertHasViolation( violationsFor(desktopRoot, currentConfig, baseConfig), /^src\/renderer\/legacy-session-helper\.ts: new or increased bridgePaths debt window\.maka\.sessions\.list$/u, ); }, ); }); it('ratchets capability debt owned directly by a shared Desktop intermediary', async () => { const sharedPath = 'src/shared/root-bridge.ts'; await withDesktopFixture( { [TRANSITIVE_APP_SHELL_PATH]: ` import { rootBridge } from '../shared/root-bridge.js'; export const AppShell = rootBridge; `, [sharedPath]: ` import { useState } from 'react'; export function rootBridge() { void window.maka.sessions.list(); localStorage.getItem('shared-root-state'); return useState(false); } `, }, (desktopRoot) => { const currentConfig = generateArchitectureConfig( desktopRoot, transitiveAppShellSeedConfig(), ); assert.deepEqual(Object.keys(currentConfig.legacyAppShell.closure), [sharedPath]); const baseConfig = structuredClone(currentConfig); const baseSharedDebt = baseConfig.legacyAppShell.closure[sharedPath]; baseSharedDebt.hookCalls = {}; baseSharedDebt.bridgePaths = {}; baseSharedDebt.environmentCapabilities = {}; const violations = violationsFor(desktopRoot, currentConfig, baseConfig); assertHasViolation( violations, /^src\/shared\/root-bridge\.ts: new or increased hookCalls debt useState$/u, ); assertHasViolation( violations, /^src\/shared\/root-bridge\.ts: new or increased bridgePaths debt window\.maka\.sessions\.list$/u, ); assertHasViolation( violations, /^src\/shared\/root-bridge\.ts: new or increased environmentCapabilities debt localStorage\.getItem$/u, ); }, ); }); it('allows support debt to leave the AppShell closure but rejects the reverse ownership regression', async () => { const mainPath = 'src/renderer/main.tsx'; const compositionPath = 'src/renderer/composition/desktop-application.ts'; const sharedPath = 'src/shared/root-bridge.ts'; const appShellOwnsSupport = ` import { rootBridge } from '../shared/root-bridge.js'; export const AppShell = rootBridge; `; const compositionUsesAppShell = ` import { AppShell } from '../app-shell.js'; export const DesktopApplication = AppShell; `; const compositionOwnsSupport = ` import { rootBridge } from '../../shared/root-bridge.js'; export const DesktopApplication = rootBridge; `; await withDesktopFixture( { [mainPath]: ` import { DesktopApplication } from './composition/desktop-application.js'; export const main = DesktopApplication; `, [compositionPath]: compositionUsesAppShell, [TRANSITIVE_APP_SHELL_PATH]: appShellOwnsSupport, [sharedPath]: `export const rootBridge = window.maka.sessions.list();`, }, async (desktopRoot) => { const seedConfig = architectureConfig({ rootDebt: { [mainPath]: emptyDebt() }, ownership: [ { capability: 'fixture-app-shell', targetZone: 'shell', legacyPaths: [TRANSITIVE_APP_SHELL_PATH], }, { capability: 'fixture-root', targetZone: 'bootstrap', legacyPaths: [mainPath], }, ], }); const appShellOwnedConfig = generateArchitectureConfig(desktopRoot, seedConfig); assert.deepEqual(Object.keys(appShellOwnedConfig.legacyAppShell.closure), [sharedPath]); assert.deepEqual(Object.keys(appShellOwnedConfig.rootDebtClosure), []); await writeFile(join(desktopRoot, compositionPath), compositionOwnsSupport, 'utf8'); await writeFile( join(desktopRoot, TRANSITIVE_APP_SHELL_PATH), `export const AppShell = true;`, 'utf8', ); const rootOwnedConfig = generateArchitectureConfig(desktopRoot, appShellOwnedConfig); assert.deepEqual(Object.keys(rootOwnedConfig.legacyAppShell.closure), []); assert.deepEqual(Object.keys(rootOwnedConfig.rootDebtClosure), [sharedPath]); assert.deepEqual( violationsFor(desktopRoot, rootOwnedConfig, appShellOwnedConfig), [], ); await writeFile(join(desktopRoot, compositionPath), compositionUsesAppShell, 'utf8'); await writeFile( join(desktopRoot, TRANSITIVE_APP_SHELL_PATH), appShellOwnsSupport, 'utf8', ); const regressedConfig = generateArchitectureConfig(desktopRoot, rootOwnedConfig); assertHasViolation( violationsFor(desktopRoot, regressedConfig, rootOwnedConfig), /^src\/shared\/root-bridge\.ts: new legacyAppShellClosure debt entries are forbidden$/u, ); }, ); }); it('rejects stateful hook growth inside a transitive legacy AppShell helper', async () => { await withDesktopFixture( transitiveAppShellFiles(` import { useState } from 'react'; export function legacySessionHelper() { const [session] = useState('legacy-session'); return session; } `), (desktopRoot) => { const currentConfig = generateArchitectureConfig( desktopRoot, transitiveAppShellSeedConfig(), ); const baseConfig = structuredClone(currentConfig); baseConfig.legacyAppShell.closure[TRANSITIVE_LEGACY_HELPER_PATH].hookCalls = {}; const violations = violationsFor(desktopRoot, currentConfig, baseConfig); assertHasViolation( violations, /^src\/renderer\/legacy-session-helper\.ts: hookCalls debt increased from 0 to 1$/u, ); assertHasViolation( violations, /^src\/renderer\/legacy-session-helper\.ts: new or increased hookCalls debt useState$/u, ); }, ); }); it('rejects a feature controller Hook returning to AppShell after provider migration', async () => { const providerOwnedAppShell = ` import { GoalProvider } from './features/goals/index.js'; export const AppShell = GoalProvider; `; await withDesktopFixture( { [TRANSITIVE_APP_SHELL_PATH]: providerOwnedAppShell, 'src/renderer/features/goals/index.ts': ` export const GoalProvider = true; export function useGoalController() { return true; } `, }, async (desktopRoot) => { const seedConfig = transitiveAppShellSeedConfig(); const providerOwnedConfig = generateArchitectureConfig(desktopRoot, seedConfig); await writeFile( join(desktopRoot, TRANSITIVE_APP_SHELL_PATH), ` import { GoalProvider, useGoalController } from './features/goals/index.js'; export const AppShell = [GoalProvider, useGoalController()]; `, 'utf8', ); const regressedConfig = generateArchitectureConfig( desktopRoot, providerOwnedConfig, ); const violations = violationsFor( desktopRoot, regressedConfig, providerOwnedConfig, ); assertHasViolation( violations, /^src\/renderer\/app-shell\.ts: hookCalls debt increased from 0 to 1$/u, ); assertHasViolation( violations, /^src\/renderer\/app-shell\.ts: new or increased hookCalls debt useGoalController$/u, ); }, ); }); it('rejects bridge and environment capability growth inside a transitive legacy AppShell helper', async () => { await withDesktopFixture( transitiveAppShellFiles(` export function legacySessionHelper() { void window.maka.sessions.list(); return localStorage.getItem('legacy-session'); } `), (desktopRoot) => { const currentConfig = generateArchitectureConfig( desktopRoot, transitiveAppShellSeedConfig(), ); const baseConfig = structuredClone(currentConfig); const baseHelperDebt = baseConfig.legacyAppShell.closure[TRANSITIVE_LEGACY_HELPER_PATH]; baseHelperDebt.bridgePaths = {}; baseHelperDebt.environmentCapabilities = {}; const violations = violationsFor(desktopRoot, currentConfig, baseConfig); assertHasViolation( violations, /^src\/renderer\/legacy-session-helper\.ts: new or increased bridgePaths debt window\.maka\.sessions\.list$/u, ); assertHasViolation( violations, /^src\/renderer\/legacy-session-helper\.ts: new or increased environmentCapabilities debt localStorage\.getItem$/u, ); }, ); }); it('rejects dependency growth inside a transitive legacy AppShell helper', async () => { const dependencyPath = 'src/renderer/legacy-session-store.ts'; await withDesktopFixture( transitiveAppShellFiles( ` import { legacySessionStore } from './legacy-session-store.js'; export const legacySessionHelper = legacySessionStore; `, { [dependencyPath]: `export const legacySessionStore = 'legacy-session';`, }, ), (desktopRoot) => { const currentConfig = generateArchitectureConfig( desktopRoot, transitiveAppShellSeedConfig(), ); const baseConfig = structuredClone(currentConfig); baseConfig.legacyAppShell.closure[TRANSITIVE_LEGACY_HELPER_PATH].dependencyPaths = {}; const violations = violationsFor(desktopRoot, currentConfig, baseConfig); assertHasViolation( violations, /^src\/renderer\/legacy-session-helper\.ts: dependencyPaths debt increased from 0 to 1$/u, ); assertHasViolation( violations, /^src\/renderer\/legacy-session-helper\.ts: new dependency debt \.\/legacy-session-store\.js$/u, ); }, ); }); it('rejects a stale AppShell closure ledger when another legacy file becomes reachable', async () => { const newlyReachablePath = 'src/renderer/legacy-session-store.ts'; await withDesktopFixture( transitiveAppShellFiles(` export const legacySessionHelper = 'legacy-session'; `), async (desktopRoot) => { const staleConfig = generateArchitectureConfig( desktopRoot, transitiveAppShellSeedConfig(), ); const expandedHelperSource = ` import { legacySessionStore } from './legacy-session-store.js'; export const legacySessionHelper = legacySessionStore; `; await writeFile( join(desktopRoot, TRANSITIVE_LEGACY_HELPER_PATH), expandedHelperSource, 'utf8', ); await writeFile( join(desktopRoot, newlyReachablePath), `export const legacySessionStore = 'legacy-session';`, 'utf8', ); staleConfig.legacyRendererFiles = [ ...staleConfig.legacyRendererFiles, newlyReachablePath, ].sort(); staleConfig.legacyAppShell.closure[TRANSITIVE_LEGACY_HELPER_PATH] = capabilityDebtForSource( expandedHelperSource, TRANSITIVE_LEGACY_HELPER_PATH, ); assertHasViolation( violationsFor(desktopRoot, staleConfig), /legacy AppShell transitive renderer closure changed;.*legacy-session-store\.ts/u, ); }, ); }); it('rejects non-static dependency escape hatches inside the AppShell legacy closure', async () => { await withDesktopFixture( transitiveAppShellFiles( ` export const hiddenLegacyOwners = import.meta.glob('./legacy-session-store.ts'); `, { 'src/renderer/legacy-session-store.ts': ` void window.maka.sessions.list(); `, }, ), (desktopRoot) => { assert.throws( () => generateArchitectureConfig(desktopRoot, transitiveAppShellSeedConfig()), /legacy-session-helper\.ts: AppShell closure contains a non-static import, require, or import\.meta glob/u, ); }, ); }); it('rejects stateful shell hooks and imports from legacy implementation', async () => { await withDesktopFixture( { 'src/renderer/legacy-session-owner.ts': 'export const legacySessionOwner = true;', 'src/renderer/shell/shell-frame.tsx': ` import * as React from 'react'; import { legacySessionOwner } from '../legacy-session-owner.js'; export function ShellFrame() { const [open] = React.useState(legacySessionOwner); return
; } `, 'src/renderer/shell/custom-hook.ts': ` import { useExternalSession } from '@example/session-hooks'; export const session = useExternalSession(); `, 'src/renderer/shell/namespace-hook.ts': ` import * as hooks from '@example/session-hooks'; export const session = hooks.useExternalSession(); `, 'src/renderer/shell/react-namespace-alias.ts': ` import * as React from 'react'; const R = React; export const state = R.useState(false); `, 'src/renderer/shell/react-19-hooks.tsx': ` import { useActionState, useInsertionEffect, useOptimistic, useTransition } from 'react'; export function StatefulFrame() { useActionState(async (state: number) => state, 0); useInsertionEffect(() => undefined, []); useOptimistic(0); useTransition(); return null; } `, 'src/renderer/composition/class-lifecycle.tsx': ` import * as React from 'react'; export class DesktopComposition extends React.Component { componentDidMount() {} render() { return null; } } `, 'src/renderer/composition/class-field-lifecycle.tsx': ` import * as React from 'react'; export class DesktopComposition extends React.Component { componentDidMount = () => {}; render() { return null; } } `, 'src/renderer/composition/class-state.tsx': ` import * as React from 'react'; export class StatefulComposition extends React.Component { constructor() { super({}); this.state = { mounted: false }; } render() { return null; } } `, 'src/renderer/composition/feature-namespace-hook.ts': ` import * as session from '../features/alpha/index.js'; export const controller = session.useSessionController(); `, 'src/renderer/features/alpha/index.ts': ` export function useSessionController() { return {}; } `, 'src/renderer/composition/plain-class.ts': ` export class DomainLifecycleName { componentDidMount() { return 'not React'; } } `, }, (desktopRoot) => { const violations = violationsFor(desktopRoot); assertHasViolation( violations, /^src\/renderer\/shell\/shell-frame\.tsx: shell code owns stateful React hooks$/u, ); assertHasViolation( violations, /^src\/renderer\/shell\/shell-frame\.tsx: shell imports forbidden implementation module:/u, ); assertHasViolation( violations, /^src\/renderer\/shell\/custom-hook\.ts: shell code owns stateful React hooks$/u, ); assertHasViolation( violations, /^src\/renderer\/shell\/namespace-hook\.ts: shell code owns stateful React hooks$/u, ); assertHasViolation( violations, /^src\/renderer\/shell\/react-namespace-alias\.ts: shell code owns stateful React hooks$/u, ); assertHasViolation( violations, /^src\/renderer\/shell\/react-19-hooks\.tsx: shell code owns stateful React hooks$/u, ); assertHasViolation( violations, /^src\/renderer\/composition\/class-lifecycle\.tsx: composition code owns React class lifecycle methods$/u, ); assertHasViolation( violations, /^src\/renderer\/composition\/class-field-lifecycle\.tsx: composition code owns React class lifecycle methods$/u, ); assertHasViolation( violations, /^src\/renderer\/composition\/class-state\.tsx: composition code owns React class lifecycle methods$/u, ); assertHasViolation( violations, /^src\/renderer\/composition\/feature-namespace-hook\.ts: composition code owns stateful React hooks$/u, ); assert.ok( !violations.some((violation) => violation.includes('plain-class.ts')), `Non-React classes must not be treated as React lifecycle owners:\n${violations.join('\n')}`, ); }, ); }); it('rejects bridge debt that grows relative to the base ledger', async () => { const debtPath = 'src/renderer/legacy-root.ts'; const currentConfig = architectureConfig({ rootDebt: { [debtPath]: emptyDebt({ bridgePaths: { 'window.maka.sessions.list': 1 } }), }, }); const baseConfig = architectureConfig({ rootDebt: { [debtPath]: emptyDebt(), }, }); await withDesktopFixture( { [debtPath]: 'void window.maka.sessions.list();', }, (desktopRoot) => { const violations = violationsFor(desktopRoot, currentConfig, baseConfig); assertHasViolation( violations, /^src\/renderer\/legacy-root\.ts: bridgePaths debt increased from 0 to 1$/u, ); assertHasViolation( violations, /^src\/renderer\/legacy-root\.ts: new or increased bridgePaths debt window\.maka\.sessions\.list$/u, ); }, ); }); it('accepts the single pinned renderer module entry', async () => { await withDesktopFixture( rendererEntryContractFiles(), (desktopRoot) => { const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); assert.deepEqual(checkRendererArchitecture({ config, desktopRoot }), []); }, ); }); it('rejects replacing the pinned renderer module entry with an alternate source', async () => { await withDesktopFixture( rendererEntryContractFiles({ 'src/renderer/index.html': ` `, 'src/renderer/settings/alternate-entry.tsx': ` void window.maka.sessions.list(); export const alternateEntry = true; `, }), (desktopRoot) => { const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); assertHasViolation( checkRendererArchitecture({ config, desktopRoot }), /^src\/renderer\/index\.html: renderer entry contract requires exactly one empty external module script for \/main\.tsx$/u, ); }, ); }); it('rejects adding another renderer module entry beside the pinned source', async () => { await withDesktopFixture( rendererEntryContractFiles({ 'src/renderer/index.html': ` `, 'src/renderer/settings/alternate-entry.tsx': `export const alternateEntry = true;`, }), (desktopRoot) => { const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); assertHasViolation( checkRendererArchitecture({ config, desktopRoot }), /^src\/renderer\/index\.html: renderer entry contract requires exactly one empty external module script for \/main\.tsx$/u, ); }, ); }); it('rejects changing the packaged main-window loader to an alternate renderer document', async () => { await withDesktopFixture( rendererEntryContractFiles({ 'src/main/main-renderer-loader.ts': ` import { join } from 'node:path'; import { pathToFileURL } from 'node:url'; interface MainRendererWindow { loadFile(path: string): Promise; loadURL(url: string): Promise; } interface MainRendererEntry { readonly filePath: string; readonly url: string; readonly useDevServer: boolean; } export function resolveMainRendererEntry( mainModuleDirectory: string, viteDevServerUrl: string | undefined, ): MainRendererEntry { const rendererEntryPath = join(mainModuleDirectory, '..', '..', 'dist-renderer', 'alternate.html'); const rendererEntryUrl = viteDevServerUrl ?? pathToFileURL(rendererEntryPath).href; return Object.freeze({ filePath: rendererEntryPath, url: rendererEntryUrl, useDevServer: !!viteDevServerUrl, }); } export async function loadMainRenderer( mainWindow: MainRendererWindow, rendererEntry: MainRendererEntry, ): Promise { if (rendererEntry.useDevServer) { await mainWindow.loadURL(rendererEntry.url); } else { await mainWindow.loadFile(rendererEntry.filePath); } } `, }), (desktopRoot) => { const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); assertHasViolation( checkRendererArchitecture({ config, desktopRoot }), /^src\/main\/main-renderer-loader\.ts: renderer loader must load only the pinned dist-renderer\/index\.html entry$/u, ); }, ); }); it('rejects shadowing the pinned renderer path helpers behind local bindings', async () => { const files = rendererEntryContractFiles(); files['src/main/main-renderer-loader.ts'] = files['src/main/main-renderer-loader.ts'] .replace("import { join } from 'node:path';", "import { join as pathJoin } from 'node:path';") .replace( "import { pathToFileURL } from 'node:url';", ` import { pathToFileURL as nodePathToFileURL } from 'node:url'; function join(...parts) { return pathJoin(...parts.slice(0, -1), 'alternate.html'); } function pathToFileURL(path) { return nodePathToFileURL(path); } `, ); await withDesktopFixture(files, (desktopRoot) => { const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); assertHasViolation( checkRendererArchitecture({ config, desktopRoot }), /^src\/main\/main-renderer-loader\.ts: renderer loader must load only the pinned dist-renderer\/index\.html entry$/u, ); }); }); it('rejects reassigning the resolved renderer entry before loading it', async () => { const files = rendererEntryContractFiles(); files['src/main/main-window.ts'] = files['src/main/main-window.ts'] .replace('const rendererEntry =', 'let rendererEntry =') .replace( 'await loadMainRenderer(mainWindow, rendererEntry);', ` rendererEntry = { filePath: '/alternate.html', url: 'file:///alternate.html', useDevServer: false, }; await loadMainRenderer(mainWindow, rendererEntry); `, ); await withDesktopFixture(files, (desktopRoot) => { const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); assertHasViolation( checkRendererArchitecture({ config, desktopRoot }), /^src\/main\/main-window\.ts: main window must delegate exactly once to the pinned renderer loader$/u, ); }); }); it('rejects mutating the frozen renderer entry before loading it', async () => { const files = rendererEntryContractFiles(); files['src/main/main-window.ts'] = files['src/main/main-window.ts'].replace( 'await loadMainRenderer(mainWindow, rendererEntry);', ` rendererEntry.filePath = '/alternate.html'; await loadMainRenderer(mainWindow, rendererEntry); `, ); await withDesktopFixture(files, (desktopRoot) => { const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); assertHasViolation( checkRendererArchitecture({ config, desktopRoot }), /^src\/main\/main-window\.ts: main window must delegate exactly once to the pinned renderer loader$/u, ); }); }); it('rejects aliasing the main window navigation API around the dedicated loader', async () => { await withDesktopFixture( rendererEntryContractFiles({ 'src/main/main-window.ts': ` import { loadMainRenderer, resolveMainRendererEntry } from './main-renderer-loader.js'; async function createWindow() { const rendererEntry = resolveMainRendererEntry(import.meta.dirname, process.env.VITE_DEV_SERVER_URL); await loadMainRenderer(mainWindow, rendererEntry); const alternateEntryPath = join(import.meta.dirname, '..', '..', 'dist-renderer', 'alternate.html'); const loadAlternateEntry = mainWindow.loadFile.bind(mainWindow); await loadAlternateEntry(alternateEntryPath); } `, }), (desktopRoot) => { const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); assertHasViolation( checkRendererArchitecture({ config, desktopRoot }), /^src\/main\/main-window\.ts: main window must delegate exactly once to the pinned renderer loader$/u, ); }, ); }); it('rejects a computed second navigation inside the dedicated loader branch', async () => { const files = rendererEntryContractFiles(); files['src/main/main-renderer-loader.ts'] = files['src/main/main-renderer-loader.ts'].replace( 'await mainWindow.loadFile(rendererEntry.filePath);', ` await mainWindow.loadFile(rendererEntry.filePath); await mainWindow['load' + 'File']( rendererEntry.filePath.replace('index.html', 'alternate.html'), ); `, ); await withDesktopFixture(files, (desktopRoot) => { const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); assertHasViolation( checkRendererArchitecture({ config, desktopRoot }), /^src\/main\/main-renderer-loader\.ts: renderer loader must load only the pinned dist-renderer\/index\.html entry$/u, ); }); }); it('rejects remapping the Vite renderer root to an alternate source tree', async () => { await withDesktopFixture( rendererEntryContractFiles({ 'vite.config.ts': ` export default defineConfig({ root: 'src/renderer/settings/alternate-root', build: { outDir: '../../../../dist-renderer' }, }); `, }), (desktopRoot) => { const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); assertHasViolation( checkRendererArchitecture({ config, desktopRoot }), /^vite\.config\.ts: Vite must build src\/renderer\/index\.html into dist-renderer without an input override$/u, ); }, ); }); it('rejects shadowing the pinned Vite config factory behind a local binding', async () => { const files = rendererEntryContractFiles(); files['vite.config.ts'] = files['vite.config.ts'] .replace("import { defineConfig } from 'vite';", "import { defineConfig as viteDefineConfig } from 'vite';") .replace( "const REPO_ROOT = '/fixture';", ` const REPO_ROOT = '/fixture'; function defineConfig(config) { return viteDefineConfig(config); } `, ); await withDesktopFixture(files, (desktopRoot) => { const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); assertHasViolation( checkRendererArchitecture({ config, desktopRoot }), /^vite\.config\.ts: Vite must build src\/renderer\/index\.html into dist-renderer without an input override$/u, ); }); }); it('rejects pointing the Vite entry guard at a different renderer root', async () => { const files = rendererEntryContractFiles(); files['vite.config.ts'] = files['vite.config.ts'].replace( "rendererEntryContractPlugin(resolve(import.meta.dirname, 'src/renderer'))", "rendererEntryContractPlugin(resolve(import.meta.dirname, 'src/renderer/settings/alternate-root'))", ); await withDesktopFixture(files, (desktopRoot) => { const config = generateArchitectureConfig(desktopRoot, rendererEntrySeedConfig()); assertHasViolation( checkRendererArchitecture({ config, desktopRoot }), /^vite\.config\.ts: Vite must build src\/renderer\/index\.html into dist-renderer without an input override$/u, ); }); }); it('attests the canonical main source in the final Vite entry graph', () => { assert.doesNotThrow(() => runRendererEntryBundleContract([ '\0vite/modulepreload-polyfill.js', '/fixture/src/renderer/index.html?html-proxy&inline-css&index=0.css', '/fixture/src/renderer/main.tsx', ]), ); }); it('does not apply the renderer entry contract to Storybook builds', () => { const plugin = rendererEntryContractPlugin('/fixture/src/renderer'); plugin.configResolved({ root: '/fixture/storybook' }); assert.doesNotThrow(() => plugin.generateBundle.call( { emitFile() { assert.fail('a non-renderer build must not emit a renderer attestation'); }, }, {}, {}, ), ); }); it('rejects a Vite HTML transform that swaps the final entry graph', () => { assert.throws( () => runRendererEntryBundleContract([ '\0vite/modulepreload-polyfill.js', '/fixture/src/renderer/settings/alternate-entry.tsx', ]), /renderer entry contract requires src\/renderer\/index\.html to import only src\/renderer\/main\.tsx/u, ); }); it('rejects executable HTML injected around the canonical module entry', () => { assert.doesNotThrow(() => assertRendererEntryHtml(canonicalRendererEntryHtml())); const emittedHtml = canonicalRendererEntryHtml().replace( '/main.tsx', './assets/index-canonical.js', ); assert.doesNotThrow(() => assertRendererEntryHtml(emittedHtml, './assets/index-canonical.js'), ); assert.throws( () => assertRendererEntryHtml(emittedHtml, './assets/index-other.js'), /renderer entry HTML contract forbids transformed executable or navigation surfaces/u, ); assert.throws( () => assertRendererEntryHtml( canonicalRendererEntryHtml( '', "script-src 'self' 'unsafe-inline'", ), ), /renderer entry HTML contract forbids transformed executable or navigation surfaces/u, ); assert.throws( () => assertRendererEntryHtml( canonicalRendererEntryHtml(''), ), /renderer entry HTML contract forbids transformed executable or navigation surfaces/u, ); }); it('rejects a newly allowlisted root debt file relative to the base ledger', async () => { const debtPath = 'src/renderer/new-root-debt.ts'; const currentConfig = architectureConfig({ rootDebt: { [debtPath]: emptyDebt() }, }); const baseConfig = architectureConfig(); await withDesktopFixture( { [debtPath]: 'export const newRootDebt = true;', }, (desktopRoot) => { const violations = violationsFor(desktopRoot, currentConfig, baseConfig); assertHasViolation( violations, /^src\/renderer\/new-root-debt\.ts: new rootDebt debt entries are forbidden$/u, ); }, ); }); it('rejects deleting root debt while the existing root entry is still dirty', async () => { const debtPath = 'src/renderer/main.tsx'; const currentConfig = architectureConfig({ legacyRendererFiles: [debtPath] }); const baseConfig = architectureConfig({ legacyRendererFiles: [debtPath], rootDebt: { [debtPath]: emptyDebt({ bridgePaths: { 'window.maka.sessions.list': 1 }, hookCalls: { useEffect: 1 }, }), }, }); await withDesktopFixture( { [debtPath]: ` import { useEffect } from 'react'; void window.maka.sessions.list(); export function RendererEntry() { useEffect(() => undefined, []); return null; } `, }, (desktopRoot) => { const violations = violationsFor(desktopRoot, currentConfig, baseConfig); assertHasViolation( violations, /^src\/renderer\/main\.tsx: permanent root entry guard cannot be removed while the source exists$/u, ); }, ); }); it('rejects removing the permanent root guard while a clean thin entry still exists', async () => { const debtPath = 'src/renderer/main.tsx'; const currentConfig = architectureConfig({ legacyRendererFiles: [debtPath] }); const baseConfig = architectureConfig({ legacyRendererFiles: [debtPath], rootDebt: { [debtPath]: emptyDebt({ bridgePaths: { 'window.maka.onboarding.getSnapshot': 1 }, hookCalls: { useEffect: 1 }, }), }, }); await withDesktopFixture( { [debtPath]: ` import { mountDesktopApplication } from './bootstrap/mount-desktop-application.js'; mountDesktopApplication(); `, 'src/renderer/bootstrap/mount-desktop-application.ts': ` export function mountDesktopApplication() {} `, }, (desktopRoot) => { assertHasViolation( violationsFor(desktopRoot, currentConfig, baseConfig), /^src\/renderer\/main\.tsx: permanent root entry guard cannot be removed while the source exists$/u, ); }, ); }); it('rejects bridge debt returning to a retained clean root entry', async () => { const debtPath = 'src/renderer/main.tsx'; const cleanSource = ` import { mountDesktopApplication } from './bootstrap/mount-desktop-application.js'; mountDesktopApplication(); `; const pollutedSource = ` import { mountDesktopApplication } from './bootstrap/mount-desktop-application.js'; void window.maka.sessions.list(); mountDesktopApplication(); `; const baseConfig = architectureConfig({ legacyRendererFiles: [debtPath], rootDebt: { [debtPath]: debtForSource(cleanSource, debtPath) }, }); const currentConfig = architectureConfig({ legacyRendererFiles: [debtPath], rootDebt: { [debtPath]: debtForSource(pollutedSource, debtPath) }, }); await withDesktopFixture( { [debtPath]: pollutedSource, 'src/renderer/bootstrap/mount-desktop-application.ts': ` export function mountDesktopApplication() {} `, }, (desktopRoot) => { const violations = violationsFor(desktopRoot, currentConfig, baseConfig); assertHasViolation( violations, /^src\/renderer\/main\.tsx: bridgePaths debt increased from 0 to 1$/u, ); assertHasViolation( violations, /^src\/renderer\/main\.tsx: new or increased bridgePaths debt window\.maka\.sessions\.list$/u, ); }, ); }); it('allows removing the permanent root guard after its source is deleted', async () => { const debtPath = 'src/renderer/main.tsx'; const currentConfig = architectureConfig(); const baseConfig = architectureConfig({ legacyRendererFiles: [debtPath], rootDebt: { [debtPath]: emptyDebt() }, }); await withDesktopFixture( { 'src/renderer/bootstrap/mount-desktop-application.ts': ` export function mountDesktopApplication() {} `, }, (desktopRoot) => { assert.deepEqual(violationsFor(desktopRoot, currentConfig, baseConfig), []); }, ); }); it('rejects retiring root debt by moving state and storage ownership into bootstrap', async () => { const debtPath = 'src/renderer/main.tsx'; const currentConfig = architectureConfig({ legacyRendererFiles: [debtPath] }); const baseConfig = architectureConfig({ legacyRendererFiles: [debtPath], rootDebt: { [debtPath]: emptyDebt() }, }); await withDesktopFixture( { [debtPath]: ` import { PollutedBootstrap } from './bootstrap/polluted-bootstrap.js'; void PollutedBootstrap; `, 'src/renderer/bootstrap/polluted-bootstrap.tsx': ` import { useState } from 'react'; export function PollutedBootstrap() { const [value] = useState(() => localStorage.getItem('root-state')); return
{value}
; } `, }, (desktopRoot) => { const violations = violationsFor(desktopRoot, currentConfig, baseConfig); assertHasViolation( violations, /bootstrap\/polluted-bootstrap\.tsx: bootstrap code owns stateful React hooks/u, ); assertHasViolation( violations, /bootstrap\/polluted-bootstrap\.tsx: bootstrap code directly accesses browser environment capabilities/u, ); assertHasViolation( violations, /^src\/renderer\/main\.tsx: permanent root entry guard cannot be removed while the source exists$/u, ); }, ); }); it('allows new legacy files inside an explicitly approved growth directory', async () => { const growthDirectory = 'src/renderer/settings'; const path = `${growthDirectory}/new-preference.ts`; const currentConfig = architectureConfig({ legacyGrowthDirectories: [growthDirectory], legacyRendererFiles: [path], }); const baseConfig = architectureConfig({ legacyGrowthDirectories: [growthDirectory], }); await withDesktopFixture( { [path]: 'export const newPreference = true;', }, (desktopRoot) => { assert.deepEqual(violationsFor(desktopRoot, currentConfig, baseConfig), []); }, ); }); it('rejects arbitrary new unclassified renderer source names relative to the base ledger', async () => { const path = 'src/renderer/session-coordinator.mts'; const currentConfig = architectureConfig({ legacyRendererFiles: [path] }); const baseConfig = architectureConfig(); await withDesktopFixture( { [path]: 'export const coordinator = true;', }, (desktopRoot) => { const violations = violationsFor(desktopRoot, currentConfig, baseConfig); assertHasViolation( violations, /^src\/renderer\/session-coordinator\.mts: new unclassified renderer source files are forbidden outside approved legacy directories$/u, ); }, ); }); it('rejects unbudgeted legacy imports from every strict ownership zone', async () => { await withDesktopFixture( { 'src/renderer/legacy-helper.ts': 'export const legacy = true;', 'src/renderer/features/alpha/controller.ts': ` import { legacy } from '../../legacy-helper.js'; export const featureValue = legacy; `, 'src/renderer/application/session/session-scope.ts': ` import { legacy } from '../../legacy-helper.js'; export const applicationValue = legacy; `, 'src/renderer/composition/desktop-application.ts': ` import { legacy } from '../legacy-helper.js'; export const compositionValue = legacy; `, 'src/renderer/platform/desktop/create-services.ts': ` import { legacy } from '../../legacy-helper.js'; export const platformValue = legacy; `, }, (desktopRoot) => { const violations = violationsFor(desktopRoot); assertHasViolation(violations, /feature imports unbudgeted renderer legacy code/u); assertHasViolation(violations, /application authority imports an outer implementation/u); assertHasViolation(violations, /composition imports a forbidden implementation module/u); assertHasViolation(violations, /Desktop adapter imports unbudgeted renderer legacy code/u); }, ); }); it('detects bridge aliases, TypeScript wrappers, and aliased React hooks', async () => { await withDesktopFixture( { 'src/renderer/application/bridge-aliases.ts': ` const desktopWindow = window; void desktopWindow.maka.sessions.list(); void globalThis.maka.sessions.list(); void self['maka'].sessions.list(); void (window as Window).maka.sessions.list(); void window!.maka.sessions.list(); const { maka } = window; void maka.sessions.list(); `, 'src/renderer/shell/aliased-hook.ts': ` import { useState as state } from 'react'; export function useFrameState() { return state(false); } `, }, (desktopRoot) => { const violations = violationsFor(desktopRoot); assertHasViolation(violations, /bridge-aliases\.ts: application code accesses the Desktop global bridge/u); assertHasViolation(violations, /aliased-hook\.ts: shell code owns stateful React hooks/u); }, ); }); it('retains full bridge method paths after the Desktop bridge is aliased', () => { const analysis = analyzeRendererSource( ` const bridge = window.maka; void bridge.sessions.list(); const { maka: desktop } = window; void desktop.transcripts.open('session-a'); `, 'src/renderer/application/bridge-method-aliases.ts', ); assert.equal(analysis.bridgePaths['window.maka.sessions.list'], 1); assert.equal(analysis.bridgePaths['window.maka.transcripts.open'], 1); }); it('retains stateful hook ownership through local aliases', () => { const analysis = analyzeRendererSource( ` import { useState as state } from 'react'; const localState = state; localState(false); `, 'src/renderer/shell/hook-method-aliases.ts', ); assert.equal(analysis.hookCalls.useState, 1); const reassigned = analyzeRendererSource( ` import { useEffect, useState } from 'react'; let hook = useState; hook = useEffect; hook(false); `, 'src/renderer/shell/reassigned-hook-alias.ts', ); assert.equal(reassigned.hookCalls.useState, 1); assert.equal(reassigned.hookCalls.useEffect, 1); const repeatedVar = analyzeRendererSource( ` import React, { useState } from 'react'; function legacyAliases() { var hook; var hook = useState; var R; var R = React; var NestedReact; if (true) { var NestedReact = React; } hook(false); R.useState(false); NestedReact.useState(false); } `, 'src/renderer/shell/repeated-var-hook-alias.ts', ); assert.equal(repeatedVar.hookCalls.useState, 3); }); it('recognizes React 19 use and namespace-destructured custom Hooks', async () => { const analysis = analyzeRendererSource( ` import { use as read } from 'react'; import * as React from 'react'; import * as hooks from '@astryxdesign/core/hooks'; const R = React; const { use: readFromNamespace } = R; const hookNamespace = hooks; const { useHotkeys: hotkeys } = hookNamespace; const { useHotkeys } = hooks as typeof hooks; const { useHotkeys: defaultHotkeys = () => undefined } = hooks as typeof hooks; read(Promise.resolve('direct')); React.use(Promise.resolve('member')); readFromNamespace(Promise.resolve('destructured')); hotkeys([]); useHotkeys([]); defaultHotkeys([]); function shadowedReact(React: { use(value: unknown): unknown }) { return React.use(Promise.resolve('shadowed')); } function shadowedHook(useHotkeys: (bindings: unknown[]) => unknown) { return useHotkeys([]); } function blockShadowedReact() { const React = { use: (value: unknown) => value }; return React.use(Promise.resolve('block-shadowed')); } function blockShadowedHook() { const useHotkeys = (bindings: unknown[]) => bindings; return useHotkeys([]); } function lateDestructuredAlias() { const lateHotkeys = lateUseHotkeys; return lateHotkeys([]); } const { useHotkeys: lateUseHotkeys } = hooks as typeof hooks; `, 'src/renderer/shell/react-use-aliases.tsx', ); assert.equal(analysis.hookCalls.use, 3); assert.equal(analysis.hookCalls.useHotkeys, 4); const nonReactUse = analyzeRendererSource( ` import { use } from './plain-helper.js'; use('not-react'); `, 'src/renderer/shell/plain-use.ts', ); assert.deepEqual(nonReactUse.hookCalls, {}); await withDesktopFixture( { 'src/renderer/shell/react-use.tsx': ` import { use } from 'react'; export function ShellRead() { return use(Promise.resolve('shell')); } `, 'src/renderer/composition/react-namespace-use.tsx': ` import * as React from 'react'; export function CompositionRead() { return React.use(Promise.resolve('composition')); } `, 'src/renderer/platform/desktop/destructured-hook.ts': ` import * as hooks from '@astryxdesign/core/hooks'; const { useHotkeys = () => undefined } = hooks as typeof hooks; export function installHotkeys() { return useHotkeys([]); } `, }, (desktopRoot) => { const violations = violationsFor(desktopRoot); assertHasViolation(violations, /shell\/react-use\.tsx: shell code owns stateful React hooks/u); assertHasViolation( violations, /composition\/react-namespace-use\.tsx: composition code owns stateful React hooks/u, ); assertHasViolation( violations, /platform\/desktop\/destructured-hook\.ts: platform code owns stateful React hooks/u, ); }, ); }); it('parses TypeScript and static-template dependencies and fails closed on dynamic paths', async () => { await withDesktopFixture( { 'src/renderer/features/alpha/import-equals.ts': ` import beta = require('../beta/private.js'); export const value = beta; `, 'src/renderer/features/alpha/dynamic.ts': ` export function load(path: string) { return import(path); } `, 'src/renderer/features/beta/private.ts': 'export const beta = true;', 'src/renderer/application/contracts/deep-type.ts': ` export type Deep = import('../../features/alpha/private.js').Deep; `, 'src/renderer/composition/static-template.ts': ` export const deep = import(\`../features/alpha/private.js\`); `, 'src/renderer/composition/import-meta-glob.ts': ` export const privateFeatures = import.meta.glob('../features/alpha/private.ts'); `, }, (desktopRoot) => { const violations = violationsFor(desktopRoot); assertHasViolation(violations, /import-equals\.ts: feature alpha imports feature beta/u); assertHasViolation(violations, /dynamic\.ts: feature code contains a non-static import or require/u); assertHasViolation(violations, /deep-type\.ts: feature imports must use index/u); assertHasViolation(violations, /static-template\.ts: feature imports must use index/u); assertHasViolation(violations, /import-meta-glob\.ts: composition code contains a non-static import or require/u); }, ); }); it('forbids exposing a feature testing entry through its public index', async () => { await withDesktopFixture( { 'src/renderer/features/alpha/index.ts': `export * from './testing.js';`, 'src/renderer/features/alpha/testing.ts': 'export const fake = true;', }, (desktopRoot) => { assertHasViolation( violationsFor(desktopRoot), /feature .*testing entry/u, ); }, ); }); it('allows legacy AppShell dependency replacement with feature public APIs and application contracts', async () => { const appShellPath = 'src/renderer/app-shell.tsx'; const appShellSource = ` import { AlphaHost } from './features/alpha/index.js'; import type { SessionScope } from './application/contracts/session-scope.js'; export function AppShell(props: { readonly scope: SessionScope }) { return ; } `; const currentDebt = debtForSource(appShellSource, appShellPath); const baseDebt = { ...currentDebt, dependencyPaths: { './legacy-alpha-owner.js': 1, './legacy-session-owner.js': 1, }, }; const ownership = [ { capability: 'fixture-app-shell', targetZone: 'shell', legacyPaths: [appShellPath], }, ]; const currentConfig = architectureConfig({ legacyFiles: { [appShellPath]: currentDebt }, legacyRendererFiles: [appShellPath], ownership, }); const baseConfig = architectureConfig({ legacyFiles: { [appShellPath]: baseDebt }, legacyRendererFiles: [appShellPath], ownership, }); await withDesktopFixture( { [appShellPath]: appShellSource, 'src/renderer/features/alpha/index.ts': ` export function AlphaHost(_props: unknown) { return null; } `, 'src/renderer/application/contracts/session-scope.ts': ` export interface SessionScope { readonly sessionId?: string } `, }, (desktopRoot) => { assert.deepEqual(violationsFor(desktopRoot, currentConfig, baseConfig), []); }, ); }); it('rejects replacing legacy AppShell debt with a feature private import', async () => { const appShellPath = 'src/renderer/app-shell.tsx'; const appShellSource = ` import { alphaController } from './features/alpha/controller.js'; export const AppShell = alphaController; `; const currentDebt = debtForSource(appShellSource, appShellPath); const baseDebt = { ...currentDebt, dependencyPaths: { './legacy-alpha-owner.js': 1 }, }; const ownership = [ { capability: 'fixture-app-shell', targetZone: 'shell', legacyPaths: [appShellPath], }, ]; const currentConfig = architectureConfig({ legacyFiles: { [appShellPath]: currentDebt }, legacyRendererFiles: [appShellPath], ownership, }); const baseConfig = architectureConfig({ legacyFiles: { [appShellPath]: baseDebt }, legacyRendererFiles: [appShellPath], ownership, }); await withDesktopFixture( { [appShellPath]: appShellSource, 'src/renderer/features/alpha/controller.ts': 'export const alphaController = true;', }, (desktopRoot) => { assertHasViolation( violationsFor(desktopRoot, currentConfig, baseConfig), /^src\/renderer\/app-shell\.tsx: new dependency debt \.\/features\/alpha\/controller\.js$/u, ); }, ); }); it('fails closed when the CLI base argument is missing or invalid', () => { const checker = fileURLToPath(new URL('./check-renderer-architecture.mjs', import.meta.url)); const repoRoot = fileURLToPath(new URL('../../..', import.meta.url)); const missing = spawnSync(process.execPath, [checker, '--base'], { cwd: repoRoot, encoding: 'utf8', }); const invalid = spawnSync(process.execPath, [checker, '--base', 'definitely-not-a-renderer-architecture-ref'], { cwd: repoRoot, encoding: 'utf8', }); assert.notEqual(missing.status, 0); assert.match(missing.stderr, /usage: check-renderer-architecture/u); assert.notEqual(invalid.status, 0); assert.match(invalid.stderr, /base ref does not resolve to a commit/u); }); });