ci(client): lint:layers rules help/ and vacay/ as helper layers

help/ holds the help center's screen registry and vacay/ a few date
helpers; both are plain data and code like utils/, yet without an entry in
RULES they could import any view unseen. Neither does today, so ruling
them costs nothing in the baseline. managed/ stays free and the comment now
says why: it is the slot an operator fills with whole screens at build time.
This commit is contained in:
Maurice
2026-10-08 12:21:50 +02:00
parent ae84f8859f
commit 17b72ff778
2 changed files with 22 additions and 2 deletions
+7 -2
View File
@@ -13,8 +13,11 @@ const VIEW = ['components', 'mobile', 'pages', 'hooks']
* may use a shared hook, and the phone screens reuse both. Nothing below the
* views (stores, repos, sync, api, the Dexie schema and the helpers) knows a
* view exists, a repo sits under the stores, and the Dexie schema under all
* of them. Directories without an entry (mobile, pages, the app root) are
* free.
* of them. help/ (the help center's screen registry, plain data) and vacay/
* (date helpers) are helpers like utils/. Directories without an entry are
* free on purpose: mobile and pages sit at the top, the app root wires
* everything, and managed/ is the slot an operator fills with whole screens
* at build time.
*/
export const RULES = {
components: ['pages', 'mobile'],
@@ -29,6 +32,8 @@ export const RULES = {
services: VIEW,
push: VIEW,
types: VIEW,
help: VIEW,
vacay: VIEW,
repo: [...VIEW, 'store'],
db: [...VIEW, 'store', 'repo', 'sync', 'api'],
}
@@ -110,6 +110,21 @@ describe('lint:layers', () => {
expect(JSON.parse(readFileSync(tree.path(BASELINE), 'utf8'))).toEqual({ 'src/components/A.tsx': 1 });
});
it('LAYERS-007: help/ and vacay/ are helpers and import no view', () => {
for (const helper of ['help', 'vacay']) {
expect(RULES[helper as keyof typeof RULES]).toEqual(
expect.arrayContaining(['components', 'mobile', 'pages', 'hooks'])
);
}
const files = {
'src/help/contexts/trip.ts': "import { Panel } from '../../components/Help/Panel'\n",
'src/vacay/yearWindow.ts': "import { useIsPhone } from '../hooks/useIsPhone'\n",
};
expect(run({}, files)).toBe(1);
expect(tree.error.join('\n')).toMatch(/src\/help\/contexts\/trip\.ts: 1 import\(s\) against the layering/);
expect(tree.error.join('\n')).toMatch(/src\/vacay\/yearWindow\.ts: 1 import\(s\) against the layering/);
});
it('LAYERS-006: a missing baseline stops the check', () => {
tree = ratchetTree({ 'src/a.ts': '' });
expect(() => check({ root: tree.root, baselinePath: tree.path(BASELINE), ...tree.out })).toThrow(RatchetError);