SOURCE / PINNED RELEASE
Made of little things.
Shiftfield
- Release
- 5a170c505f6b…
- Author-recorded commit
- a2e23e0171aa…
- License
- LICENSE
- Author’s source reference
- nostr://npub16nzc4ql748r2kv9202rgzrt0fmtk4n3tzsjl86yf6ty9agqe9u0qjcc7hc/wss%3A%2F%2Fgit.napplet.soy%2F/n-e4fb6341ccd
Archive hash verified: 66de7623efd91da1…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
import assert from 'node:assert/strict';
import { readdir, readFile } from 'node:fs/promises';
import { dirname, extname, join, relative } from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';
import { runInNewContext } from 'node:vm';
import ts from 'typescript';
const root = dirname(dirname(fileURLToPath(import.meta.url)));
const textExtensions = new Set(['.css', '.html', '.json', '.md', '.mjs', '.ts']);
const skippedDirectories = new Set(['.git', 'dist', 'node_modules', '.agents', '.claude', '.napplet-space']);
async function collectTextFiles(directory = root) {
const files = [];
for (const entry of await readdir(directory, { withFileTypes: true })) {
if (entry.isDirectory() && skippedDirectories.has(entry.name)) continue;
const path = join(directory, entry.name);
if (entry.isDirectory()) files.push(...await collectTextFiles(path));
else if (entry.isFile() && textExtensions.has(extname(entry.name))) files.push(path);
}
return files;
}
const files = await collectTextFiles();
const sources = new Map(
await Promise.all(files.map(async (path) => [relative(root, path), await readFile(path, 'utf8')])),
);
test('rejects retired app-owned bootstrap and probing APIs', () => {
const joined = (...parts) => parts.join('');
const shimSpecifier = ['@napplet', 'shim'].join('/');
const patterns = [
new RegExp(`import\\s+(?:[^'\"]+\\s+from\\s+)?['\"]${shimSpecifier}['\"]`),
new RegExp(`shell(?:\\.|\\?\\.)${joined('rea', 'dy')}\\s*\\(`),
new RegExp(`shell(?:\\.|\\?\\.)${joined('in', 'it')}\\s*\\(`),
new RegExp(`shell(?:\\.|\\?\\.)${joined('supp', 'orts')}\\s*\\(`),
new RegExp(`window(?:\\.|\\?\\.)napplet(?:\\.|\\?\\.)${joined('sh', 'ell')}`),
new RegExp(`window\\s*\\[\\s*['\"]napplet['\"]\\s*\\]\\s*(?:\\.|\\?\\.)${joined('sh', 'ell')}`),
new RegExp(`window\\s*\\[\\s*['\"]napplet['\"]\\s*\\]\\s*\\[\\s*['\"]${joined('sh', 'ell')}['\"]\\s*\\]`),
new RegExp(`${joined('discover', 'Services')}\\s*\\(`),
new RegExp(`${joined('has', 'Service')}(?:Version)?\\s*\\(`),
];
const retiredExamples = [
`import { install } from '${shimSpecifier}';`,
`shell?.${joined('rea', 'dy')}()`,
`shell.${joined('in', 'it')}()`,
`shell.${joined('supp', 'orts')}('storage')`,
`window?.napplet?.${joined('sh', 'ell')}`,
`window['napplet']?.${joined('sh', 'ell')}`,
`window['napplet']['${joined('sh', 'ell')}']`,
`${joined('discover', 'Services')}()`,
`${joined('has', 'Service', 'Version')}('relay', '1')`,
];
for (const [index, example] of retiredExamples.entries()) {
assert.match(example, patterns[index]);
}
for (const [path, source] of sources) {
for (const pattern of patterns) assert.doesNotMatch(source, pattern, path);
}
});
test('keeps normal Nostr examples OUTBOX-first', () => {
const main = sources.get('src/main.ts');
const patterns = ['query', 'subscribe', 'publish'].map(
(operation) => new RegExp(['relay', operation].join('\\.') + '\\s*\\('),
);
for (const pattern of patterns) assert.doesNotMatch(main, pattern);
const joined = (...parts) => parts.join('');
const directCallPatterns = [
new RegExp(`window(?:\\.|\\?\\.)napplet(?:\\.|\\?\\.)[A-Za-z_$][\\w$]*(?:\\.|\\?\\.)[A-Za-z_$][\\w$]*\\s*\\(`),
new RegExp(`window\\s*\\[\\s*['\"]napplet['\"]\\s*\\]\\s*\\[\\s*['\"][^'\"]+['\"]\\s*\\]\\s*(?:\\.|\\?\\.)[A-Za-z_$][\\w$]*\\s*\\(`),
];
const directCallExamples = [
`window?.napplet?.storage.${joined('get', 'Item')}('key')`,
`window['napplet']['outbox'].${joined('qu', 'ery')}([])`,
];
for (const [index, example] of directCallExamples.entries()) {
assert.match(example, directCallPatterns[index]);
}
for (const [path, source] of sources) {
if (!path.startsWith('src/')) continue;
for (const pattern of directCallPatterns) assert.doesNotMatch(source, pattern, path);
}
const designPatterns = sources.get('docs/design-patterns.md');
assert.match(designPatterns, /OUTBOX-first/i);
assert.match(designPatterns, /relay-local escape hatch/i);
});
test('ships no forked skill body, points agents at skills.sh, and keeps manifest config on documented surface', () => {
const skillBodies = [...sources.keys()].filter((path) => /(?:^|\/)SKILL\.md$/.test(path));
assert.deepEqual(skillBodies, []);
for (const path of ['AGENTS.md', 'README.md']) {
assert.match(sources.get(path), /docs\/napplet-space\.md/, path);
}
const retiredInstallers = new RegExp(
[['@napplet', 'skills'].join('/'), ['napplet skills', 'install'].join(' '), ['.codex', 'skills'].join('/')]
.map((text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
.join('|'),
);
for (const [path, source] of sources) {
assert.doesNotMatch(source, retiredInstallers, path);
}
const viteConfig = sources.get('vite.config.ts');
assert.match(viteConfig, /artifactMode:\s*'single-file'/);
// `requires` is allowed once a product has a core task that cannot run
// without a domain, but it must stay a list of bare domain names.
const requires = viteConfig.match(/\brequires\s*:\s*\[([^\]]*)\]/);
if (requires) {
const entries = requires[1].split(',').map((entry) => entry.trim()).filter(Boolean);
assert.ok(entries.length > 0, 'requires must not be an empty list; omit it instead');
for (const entry of entries) {
assert.match(entry, /^['"][a-z][a-z0-9-]*['"]$/, `requires entry ${entry} must be a bare domain name`);
}
}
assert.doesNotMatch(viteConfig, /\bconfigSchema\b/);
assert.equal(JSON.parse(sources.get('config.schema.json')).type, 'object');
const retiredSingleFilePlugin = new RegExp(['vite-plugin', 'singlefile'].join('-'));
for (const source of sources.values()) assert.doesNotMatch(source, retiredSingleFilePlugin);
});
test('keeps deferred domains out of active package surfaces', () => {
const joined = (...parts) => parts.join('');
const domains = [joined('con', 'nect'), joined('cl', 'ass')];
for (const domain of domains) {
const patterns = [
new RegExp(`@napplet/nap/${domain}\\b`),
new RegExp(`window(?:\\.|\\?\\.)napplet(?:\\.|\\?\\.)${domain}\\b`),
new RegExp(`window\\s*\\[\\s*['\"]napplet['\"]\\s*\\]\\s*\\[\\s*['\"]${domain}['\"]\\s*\\]`),
new RegExp(`import\\s*\\{[^}]*\\b${domain}\\b[^}]*\\}\\s*from\\s*['\"]@napplet/sdk['\"]`),
new RegExp(`\\b${domain.toUpperCase()}_DOMAIN\\b`),
new RegExp(`\\b${domain}\\s*(?:\\.|\\?\\.)[A-Za-z]\\w*\\s*\\(`),
new RegExp(`\\brequires\\s*:\\s*\\[[^\\]]*['\"]${domain}['\"]`),
];
const examples = [
`@napplet/nap/${domain}`,
`window?.napplet?.${domain}`,
`window['napplet']['${domain}']`,
`import { ${domain} } from '@napplet/sdk'`,
`${domain.toUpperCase()}_DOMAIN`,
`${domain}.request()`,
`requires: ['${domain}']`,
];
for (const [index, example] of examples.entries()) {
assert.match(example, patterns[index]);
}
for (const [path, source] of sources) {
for (const pattern of patterns) assert.doesNotMatch(source, pattern, path);
}
}
for (const label of [joined('NAP-', 'CON', 'NECT'), joined('NAP-', 'CLASS')]) {
for (const [path, source] of sources) {
if (source.includes(label)) assert.match(source, /deferred|retired/i, path);
}
}
});
test('treats injected optional-domain absence as a normal state', async () => {
const helperSource = await readFile(join(root, 'src/domain-availability.ts'), 'utf8');
const compiled = ts.transpileModule(helperSource, {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 },
}).outputText;
const module = { exports: {} };
runInNewContext(compiled, { module, exports: module.exports });
const { hasDomain } = module.exports;
assert.equal(hasDomain(undefined, 'outbox'), false);
assert.equal(hasDomain({}, 'outbox'), false);
assert.equal(hasDomain({ outbox: null }, 'outbox'), false);
assert.equal(hasDomain({ outbox: {} }, 'outbox'), true);
assert.equal(hasDomain(Object.create({ outbox: {} }), 'outbox'), true);
// Product code may replace every demo control; it must keep gating optional
// domains through the injected-namespace check instead of a probe API.
const main = sources.get('src/main.ts');
assert.match(main, /from '\.\/domain-availability\.js'/);
assert.match(main, /runtimeHasDomain\(/);
});
test('keeps the applet layout contract: menu panel carries the title, frame-filling root', () => {
const html = sources.get('index.html');
const title = html.match(/<title>([^<]*)<\/title>/)?.[1]?.trim();
assert.ok(title, 'index.html keeps a <title> for metadata');
// Publish title ruling 2026-10-28 (publish_title_ruling_2026-10-28.md): the
// public name is Shiftfield; the "minesweeper" word must never ship in HTML.
assert.equal(title, 'Shiftfield', 'shipped <title> is the ratified publish title');
// Newer @napplet/vite-plugin fails the build unless the NIP-5D manifest has
// a description; it reads it from this HTML meta, not from napplet.json.
const descMatch = html.match(/<meta\s+name="description"\s+content="([^"]+)"\s*\/?>/i);
const desc = descMatch?.[1];
assert.ok(desc, 'index.html keeps a description meta for the NIP-5D manifest');
// Player ruling 2026-10-29 (name_description_ruling_2026-10-29.md): the ratified
// listing tagline "— the Roguelike Minesweeper —" is the ONE sanctioned use of
// the word in shipped HTML (it sells the genre). The 2026-10-28 ban stands
// everywhere else: strip the description meta before asserting absence.
assert.doesNotMatch(html.replace(descMatch[0], ''), /minesweeper/i, 'no "minesweeper" copy outside the description meta');
const manifest = JSON.parse(sources.get('napplet.json'));
assert.equal(manifest.title, 'Shiftfield', 'manifest display title is Shiftfield');
assert.equal(manifest.description, desc, 'HTML description meta matches the manifest description');
// Player ruling 2026-10-29 (name_description_ruling_2026-10-29.md) supersedes the
// internal-name clause of the 2026-10-28 title ruling: manifest name is shiftfield.
// The codename survives only in the repo dir, save keys, engine class, and QA hook.
assert.equal(manifest.name, 'shiftfield', 'manifest project name is shiftfield');
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// Player ruling 2026-10-28 (deep_sector_menu_rename_ruling_2026-10-28.md):
// the menu dialog's heading IS the game title (it replaced "Deep Sector").
// Page-level masthead branding stays banned.
assert.match(html, new RegExp(`<h2 id="menuTitle"[^>]*>\\s*${escaped}\\s*</h2>`));
assert.doesNotMatch(html, /class="[^"]*\b(?:masthead|eyebrow|hero)\b/);
const css = sources.get('src/styles.css');
assert.doesNotMatch(css, /\bbody\s*\{[^}]*min-width/);
assert.match(css, /html,\s*body,\s*#app\s*\{[^}]*height:\s*100%/);
});
