You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/sema/sounds-grammar.test.ts

119 lines
5.0 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* Census guard — the sound catalogue is the ONLY place a sound is authored.
*
* WHY THIS EXISTS, and what it used to guard. Until 2026-08-06 the catalogue
* held parametric DELTAS (`SOUND_TUNINGS`) whose keys had to be headed by a
* sema family, because a delta only means something over a base. This file
* enforced that grammar. The law was born from a real drift: `form.*` entered
* on 2026-05-19 naming seven form controls truthfully, spread to menus and
* trees a week later, and by 2026-08-05 had fifty consumers of which exactly
* ONE was the Form. Nothing caught it for two and a half months because
* nothing looked at the keys.
*
* The grammar is gone because the reason for it is gone. A catalogue entry is
* no longer a delta over a family — it is a NAME a component may say, and the
* same `soft` serves a commit and an emerge alike, because the family still
* supplies its own identity underneath. Heading names with a family would now
* be the lie, not the truth.
*
* WHAT SURVIVES, because it is about values and not about names:
*
* 1. Nothing in the catalogue resolves to silence. Silence is `SILENT`, one
* canonical value the resolver honours by dropping the channel — never a
* number, because later layers move numbers. A "silenced" toggle once
* measured −13.9 dBFS with `risk`, louder than a real button press, for
* exactly this reason.
* 2. Every entry is playable: a complete, finite signature once resolved over
* a family base (checked in `sound-names.test.ts`, which also snapshots
* what each name sounds like).
* 3. No component authors a sound. That is now a TYPE — `Sema['cascade']`
* accepts a name or `SILENT` — but a type only guards TypeScript, so the
* census below also reads the pack SOURCES: a raw parameter bag written in
* a `.ts` the compiler happens not to narrow is still the defect.
*/
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { describe, expect, it } from 'vitest';
import { execSync } from 'node:child_process';
import { SILENT } from './channels';
import { SOUNDS, namedSound, type SoundName } from './sound-names';
const NAMES = Object.keys(SOUNDS) as SoundName[];
/**
* Pack sources, listed via git — never by walking the tree (worktrees, build/)
* — and with comments stripped, because prose that MENTIONS the old shape is
* not the old shape. Scanning raw text flagged five doc blocks the first time
* this ran.
*/
function packSources(): { file: string; source: string }[] {
const root = join(__dirname, '..', '..', '..');
const out = execSync('git ls-files src/uix/sema/components', { cwd: root, encoding: 'utf-8' });
return out
.split('\n')
.filter((f) => f.endsWith('.ts') && !f.endsWith('index.ts'))
.map((f) => ({
file: f.replace(/^.*\//, ''),
source: readFileSync(join(root, f), 'utf-8')
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/(^|\s)\/\/[^\n]*/g, '$1')
}));
}
describe('the sound catalogue is the only place a sound is authored', () => {
it('finds the catalogue (sanity: the census is not scanning an empty const)', () => {
expect(NAMES.length).toBeGreaterThan(12);
});
it('lets no name resolve to silence — silence is a value, not a level', () => {
const offenders: string[] = [];
for (const name of NAMES) {
const gain = (namedSound(name) as { gain?: unknown }).gain;
if (typeof gain === 'number' && gain <= 0) offenders.push(`${name}: gain ${gain}`);
}
expect(
offenders,
`silence is ${String(SILENT)}, one canonical value the resolver honours by dropping the ` +
'channel. A gain of zero is a NUMBER, and later layers move numbers.'
).toEqual([]);
});
it('names every entry in lowercase dotted segments', () => {
const malformed = NAMES.filter((k) => k.split('.').some((s) => !/^[a-z][a-z-]*$/.test(s)));
expect(malformed, 'a name is lowercase segments — it is typed, so it must read well').toEqual(
[]
);
});
it('lets no pack author a sound instead of naming one', () => {
const offenders: string[] = [];
for (const { file, source } of packSources()) {
// A rule may say `sound: 'name'`, `sound: SILENT`, or nothing. Anything
// else — an object literal, a call — is authoring.
for (const match of source.matchAll(/\bsound:\s*(.)/g)) {
const head = match[1];
if (head === "'" || head === 'S') continue;
const line = source.slice(0, match.index).split('\n').length;
offenders.push(`${file}:${line}`);
}
}
expect(
offenders,
'a pack NAMES a sound (`sound-names.ts`) or says SILENT. Parameters live in one place — ' +
'the measured cost of the alternative was 214 rules authoring 33 signatures, 92 of ' +
'them saying nothing but a volume'
).toEqual([]);
});
it('leaves no pack importing the retired arithmetic catalogue', () => {
const stale = packSources()
.filter(({ source }) => /soundTuning|SOUND_TUNINGS|\bsound\(/.test(source))
.map(({ file }) => file);
expect(stale, '`SOUND_TUNINGS` / `soundTuning()` / `sound()` were retired 2026-08-06').toEqual(
[]
);
});
});

Powered by TurnKey Linux.