Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 57 additions & 25 deletions eslint.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -886,6 +886,32 @@ const commentSwallowPlugin = {
},
};

// ───────────────────────────────────────────────────────────────────────────
// The trees nothing in this repo ever lints: installed dependencies and build
// output. Named ONCE and spread into the global ignore below AND into every
// per-object `ignores` beside it, so "no config object can match build output"
// is a property of this one array instead of seven lists that happen to agree.
//
// They did not agree. Four of the seven `files` objects below omitted
// `**/build/**`, `**/.next/**` and `**/.turbo/**`, so TypeScript under those
// directories resolved to three ENABLED rules while the same generated file
// under `dist/` resolved to none — the incidental-neutrality the levelling in
// #12334 replaced.
//
// ⚠️ The global object alone already keeps these paths out of the linted
// population, so the per-object copies change no behaviour today. They are not
// redundancy: they are what makes the invariant survive a future edit to the
// global object by someone who has never read this history — which is exactly
// how a rule came to be enabled on generated code in the first place.
// ───────────────────────────────────────────────────────────────────────────
const NEVER_LINTED = Object.freeze([
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/.next/**',
'**/.turbo/**',
]);

export default [
// ───────────────────────────────────────────────────────────────────────────
// GLOBAL ignore. This is the ONLY object in this array with no `files` key,
Expand All@@ -910,34 +936,41 @@ export default [
// So the accept/reject semantics of `**/dist/**` do not move: nothing there
// had a rule enabled to lose. The saving is pure enumeration + parse.
//
// ⚠️ SCOPE IS DELIBERATELY NARROWER THAN THE PER-OBJECT LISTS BELOW. Those
// also carry `**/build/**`, `**/.next/**` and `**/.turbo/**`, and promoting
// those three here would NOT be semantics-neutral. Three of the objects
// below (the `packages/**` and `examples/**` ones) list only
// `**/node_modules/**` and `**/dist/**` in their `ignores`, so they still
// match TypeScript under the other three directories:
// ⚠️ THE OTHER THREE PATTERNS ARE A NAMED CORRECTION, NOT A NEUTRAL ONE.
// `**/build/**`, `**/.next/**` and `**/.turbo/**` join this object under a
// maintainer ruling (2026-08-26, option C on #12334, verbatim 「同意」).
// Three rules are enabled on TypeScript under those directories today, and
// promoting the patterns DELIBERATELY TURNS THEM OFF on build output:
//
// packages/core/build/x.ts → LINTED, 3 rules ENABLED
// packages/core/build/x.ts before → LINTED, 3 rules ENABLED
// (slot-lookup/no-any-assignment, no-restricted-syntax,
// query-options/no-any-erasure), parser @typescript-eslint/parser
// after → no config object matches: not linted
//
// Recorded in those words rather than under a "changes nothing" banner,
// because switching a rule off on generated code and switching it off on
// source are the same line in a diff. It repairs this config's own stated
// intent — never lint build output: nobody authors the code under those
// directories, and linting generated code is the defect being removed, not
// coverage being cut.
//
// No such path exists in this repo today — nothing emits to `build/`, and the
// only Next.js app is `apps/docs`, which is outside `packages/**` — so this
// is latent rather than live. It is still a real semantic change, and it is
// left out of this object pending a maintainer ruling rather than taken
// silently under a "changes nothing" banner. See #12304.
// `**/.next/**` is LIVE, not latent (measured 2026-08-25 on #12334):
// `apps/docs/.next` is 3.9 GB after `pnpm --filter @objectstack/docs build`,
// and linting it is a hard OOM — `exit 134`, "Ineffective mark-compacts near
// heap limit", 66 s, no eslint output — which reads as a crashed gate rather
// than as 3.9 GB of chunks being parsed. A tracked-file census cannot see it:
// `.next` is untracked output, and `git ls-files` matches 0 files under any
// of the five patterns (control: 4,776 under `/src/`). `build/` and
// `.turbo/` stay latent on that census; the levelling below is what stops
// either of them becoming the next `.next`. See #12334 / #12304.
// ───────────────────────────────────────────────────────────────────────────
{
ignores: ['**/node_modules/**', '**/dist/**'],
ignores: [...NEVER_LINTED],
},
{
files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'],
ignores: [
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/.next/**',
'**/.turbo/**',
...NEVER_LINTED,
'packages/spec/**',
// CLI/scaffold templates contain `@objectstack/spec` strings that are
// emitted to user projects, not actual imports in this repo.
Expand DownExpand Up@@ -980,7 +1013,7 @@ export default [
// reason to suspect it.
{
files: ['packages/cli/src/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', '**/*.test.ts'],
ignores: [...NEVER_LINTED, '**/*.test.ts'],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1019,7 +1052,7 @@ export default [
// backward-compat fixture (#2089) and are intentional.
{
files: ['examples/**/*.{ts,tsx,mts,cts}', 'packages/apps/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', 'packages/qa/downstream-contract/**'],
ignores: [...NEVER_LINTED, 'packages/qa/downstream-contract/**'],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1068,7 +1101,7 @@ export default [
// needs type information, so it belongs to a typed-lint pass, not here.
{
files: ['packages/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', ...SLOT_LOOKUP_UNSWEPT],
ignores: [...NEVER_LINTED, ...SLOT_LOOKUP_UNSWEPT],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1138,8 +1171,7 @@ export default [
{
files: ['packages/**/*.{ts,tsx,mts,cts}'],
ignores: [
'**/node_modules/**',
'**/dist/**',
...NEVER_LINTED,
// First cut is non-test code (08-03 triage). The ratchet lifts this and
// holds the test residual to a count instead.
...QUERY_OPTIONS_TEST_GLOBS,
Expand DownExpand Up@@ -1178,7 +1210,7 @@ export default [
// fakes — the arm the guard is worth the most on.
{
files: ['**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/.turbo/**'],
ignores: [...NEVER_LINTED],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand All@@ -1204,7 +1236,7 @@ export default [
// stopped being locked.
{
files: COMMENT_SWALLOW_FILES,
ignores: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/.turbo/**'],
ignores: [...NEVER_LINTED],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 57 additions & 25 deletions eslint.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -886,6 +886,32 @@ const commentSwallowPlugin = {
},
};

// ───────────────────────────────────────────────────────────────────────────
// The trees nothing in this repo ever lints: installed dependencies and build
// output. Named ONCE and spread into the global ignore below AND into every
// per-object `ignores` beside it, so "no config object can match build output"
// is a property of this one array instead of seven lists that happen to agree.
//
// They did not agree. Four of the seven `files` objects below omitted
// `**/build/**`, `**/.next/**` and `**/.turbo/**`, so TypeScript under those
// directories resolved to three ENABLED rules while the same generated file
// under `dist/` resolved to none — the incidental-neutrality the levelling in
// #12334 replaced.
//
// ⚠️ The global object alone already keeps these paths out of the linted
// population, so the per-object copies change no behaviour today. They are not
// redundancy: they are what makes the invariant survive a future edit to the
// global object by someone who has never read this history — which is exactly
// how a rule came to be enabled on generated code in the first place.
// ───────────────────────────────────────────────────────────────────────────
const NEVER_LINTED = Object.freeze([
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/.next/**',
'**/.turbo/**',
]);

export default [
// ───────────────────────────────────────────────────────────────────────────
// GLOBAL ignore. This is the ONLY object in this array with no `files` key,
Expand All@@ -910,34 +936,41 @@ export default [
// So the accept/reject semantics of `**/dist/**` do not move: nothing there
// had a rule enabled to lose. The saving is pure enumeration + parse.
//
// ⚠️ SCOPE IS DELIBERATELY NARROWER THAN THE PER-OBJECT LISTS BELOW. Those
// also carry `**/build/**`, `**/.next/**` and `**/.turbo/**`, and promoting
// those three here would NOT be semantics-neutral. Three of the objects
// below (the `packages/**` and `examples/**` ones) list only
// `**/node_modules/**` and `**/dist/**` in their `ignores`, so they still
// match TypeScript under the other three directories:
// ⚠️ THE OTHER THREE PATTERNS ARE A NAMED CORRECTION, NOT A NEUTRAL ONE.
// `**/build/**`, `**/.next/**` and `**/.turbo/**` join this object under a
// maintainer ruling (2026-08-26, option C on #12334, verbatim 「同意」).
// Three rules are enabled on TypeScript under those directories today, and
// promoting the patterns DELIBERATELY TURNS THEM OFF on build output:
//
// packages/core/build/x.ts → LINTED, 3 rules ENABLED
// packages/core/build/x.ts before → LINTED, 3 rules ENABLED
// (slot-lookup/no-any-assignment, no-restricted-syntax,
// query-options/no-any-erasure), parser @typescript-eslint/parser
// after → no config object matches: not linted
//
// Recorded in those words rather than under a "changes nothing" banner,
// because switching a rule off on generated code and switching it off on
// source are the same line in a diff. It repairs this config's own stated
// intent — never lint build output: nobody authors the code under those
// directories, and linting generated code is the defect being removed, not
// coverage being cut.
//
// No such path exists in this repo today — nothing emits to `build/`, and the
// only Next.js app is `apps/docs`, which is outside `packages/**` — so this
// is latent rather than live. It is still a real semantic change, and it is
// left out of this object pending a maintainer ruling rather than taken
// silently under a "changes nothing" banner. See #12304.
// `**/.next/**` is LIVE, not latent (measured 2026-08-25 on #12334):
// `apps/docs/.next` is 3.9 GB after `pnpm --filter @objectstack/docs build`,
// and linting it is a hard OOM — `exit 134`, "Ineffective mark-compacts near
// heap limit", 66 s, no eslint output — which reads as a crashed gate rather
// than as 3.9 GB of chunks being parsed. A tracked-file census cannot see it:
// `.next` is untracked output, and `git ls-files` matches 0 files under any
// of the five patterns (control: 4,776 under `/src/`). `build/` and
// `.turbo/` stay latent on that census; the levelling below is what stops
// either of them becoming the next `.next`. See #12334 / #12304.
// ───────────────────────────────────────────────────────────────────────────
{
ignores: ['**/node_modules/**', '**/dist/**'],
ignores: [...NEVER_LINTED],
},
{
files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'],
ignores: [
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/.next/**',
'**/.turbo/**',
...NEVER_LINTED,
'packages/spec/**',
// CLI/scaffold templates contain `@objectstack/spec` strings that are
// emitted to user projects, not actual imports in this repo.
Expand DownExpand Up@@ -980,7 +1013,7 @@ export default [
// reason to suspect it.
{
files: ['packages/cli/src/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', '**/*.test.ts'],
ignores: [...NEVER_LINTED, '**/*.test.ts'],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1019,7 +1052,7 @@ export default [
// backward-compat fixture (#2089) and are intentional.
{
files: ['examples/**/*.{ts,tsx,mts,cts}', 'packages/apps/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', 'packages/qa/downstream-contract/**'],
ignores: [...NEVER_LINTED, 'packages/qa/downstream-contract/**'],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1068,7 +1101,7 @@ export default [
// needs type information, so it belongs to a typed-lint pass, not here.
{
files: ['packages/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', ...SLOT_LOOKUP_UNSWEPT],
ignores: [...NEVER_LINTED, ...SLOT_LOOKUP_UNSWEPT],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1138,8 +1171,7 @@ export default [
{
files: ['packages/**/*.{ts,tsx,mts,cts}'],
ignores: [
'**/node_modules/**',
'**/dist/**',
...NEVER_LINTED,
// First cut is non-test code (08-03 triage). The ratchet lifts this and
// holds the test residual to a count instead.
...QUERY_OPTIONS_TEST_GLOBS,
Expand DownExpand Up@@ -1178,7 +1210,7 @@ export default [
// fakes — the arm the guard is worth the most on.
{
files: ['**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/.turbo/**'],
ignores: [...NEVER_LINTED],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand All@@ -1204,7 +1236,7 @@ export default [
// stopped being locked.
{
files: COMMENT_SWALLOW_FILES,
ignores: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/.turbo/**'],
ignores: [...NEVER_LINTED],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 57 additions & 25 deletions eslint.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -886,6 +886,32 @@ const commentSwallowPlugin = {
},
};

// ───────────────────────────────────────────────────────────────────────────
// The trees nothing in this repo ever lints: installed dependencies and build
// output. Named ONCE and spread into the global ignore below AND into every
// per-object `ignores` beside it, so "no config object can match build output"
// is a property of this one array instead of seven lists that happen to agree.
//
// They did not agree. Four of the seven `files` objects below omitted
// `**/build/**`, `**/.next/**` and `**/.turbo/**`, so TypeScript under those
// directories resolved to three ENABLED rules while the same generated file
// under `dist/` resolved to none — the incidental-neutrality the levelling in
// #12334 replaced.
//
// ⚠️ The global object alone already keeps these paths out of the linted
// population, so the per-object copies change no behaviour today. They are not
// redundancy: they are what makes the invariant survive a future edit to the
// global object by someone who has never read this history — which is exactly
// how a rule came to be enabled on generated code in the first place.
// ───────────────────────────────────────────────────────────────────────────
const NEVER_LINTED = Object.freeze([
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/.next/**',
'**/.turbo/**',
]);

export default [
// ───────────────────────────────────────────────────────────────────────────
// GLOBAL ignore. This is the ONLY object in this array with no `files` key,
Expand All@@ -910,34 +936,41 @@ export default [
// So the accept/reject semantics of `**/dist/**` do not move: nothing there
// had a rule enabled to lose. The saving is pure enumeration + parse.
//
// ⚠️ SCOPE IS DELIBERATELY NARROWER THAN THE PER-OBJECT LISTS BELOW. Those
// also carry `**/build/**`, `**/.next/**` and `**/.turbo/**`, and promoting
// those three here would NOT be semantics-neutral. Three of the objects
// below (the `packages/**` and `examples/**` ones) list only
// `**/node_modules/**` and `**/dist/**` in their `ignores`, so they still
// match TypeScript under the other three directories:
// ⚠️ THE OTHER THREE PATTERNS ARE A NAMED CORRECTION, NOT A NEUTRAL ONE.
// `**/build/**`, `**/.next/**` and `**/.turbo/**` join this object under a
// maintainer ruling (2026-08-26, option C on #12334, verbatim 「同意」).
// Three rules are enabled on TypeScript under those directories today, and
// promoting the patterns DELIBERATELY TURNS THEM OFF on build output:
//
// packages/core/build/x.ts → LINTED, 3 rules ENABLED
// packages/core/build/x.ts before → LINTED, 3 rules ENABLED
// (slot-lookup/no-any-assignment, no-restricted-syntax,
// query-options/no-any-erasure), parser @typescript-eslint/parser
// after → no config object matches: not linted
//
// Recorded in those words rather than under a "changes nothing" banner,
// because switching a rule off on generated code and switching it off on
// source are the same line in a diff. It repairs this config's own stated
// intent — never lint build output: nobody authors the code under those
// directories, and linting generated code is the defect being removed, not
// coverage being cut.
//
// No such path exists in this repo today — nothing emits to `build/`, and the
// only Next.js app is `apps/docs`, which is outside `packages/**` — so this
// is latent rather than live. It is still a real semantic change, and it is
// left out of this object pending a maintainer ruling rather than taken
// silently under a "changes nothing" banner. See #12304.
// `**/.next/**` is LIVE, not latent (measured 2026-08-25 on #12334):
// `apps/docs/.next` is 3.9 GB after `pnpm --filter @objectstack/docs build`,
// and linting it is a hard OOM — `exit 134`, "Ineffective mark-compacts near
// heap limit", 66 s, no eslint output — which reads as a crashed gate rather
// than as 3.9 GB of chunks being parsed. A tracked-file census cannot see it:
// `.next` is untracked output, and `git ls-files` matches 0 files under any
// of the five patterns (control: 4,776 under `/src/`). `build/` and
// `.turbo/` stay latent on that census; the levelling below is what stops
// either of them becoming the next `.next`. See #12334 / #12304.
// ───────────────────────────────────────────────────────────────────────────
{
ignores: ['**/node_modules/**', '**/dist/**'],
ignores: [...NEVER_LINTED],
},
{
files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'],
ignores: [
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/.next/**',
'**/.turbo/**',
...NEVER_LINTED,
'packages/spec/**',
// CLI/scaffold templates contain `@objectstack/spec` strings that are
// emitted to user projects, not actual imports in this repo.
Expand DownExpand Up@@ -980,7 +1013,7 @@ export default [
// reason to suspect it.
{
files: ['packages/cli/src/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', '**/*.test.ts'],
ignores: [...NEVER_LINTED, '**/*.test.ts'],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1019,7 +1052,7 @@ export default [
// backward-compat fixture (#2089) and are intentional.
{
files: ['examples/**/*.{ts,tsx,mts,cts}', 'packages/apps/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', 'packages/qa/downstream-contract/**'],
ignores: [...NEVER_LINTED, 'packages/qa/downstream-contract/**'],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1068,7 +1101,7 @@ export default [
// needs type information, so it belongs to a typed-lint pass, not here.
{
files: ['packages/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', ...SLOT_LOOKUP_UNSWEPT],
ignores: [...NEVER_LINTED, ...SLOT_LOOKUP_UNSWEPT],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1138,8 +1171,7 @@ export default [
{
files: ['packages/**/*.{ts,tsx,mts,cts}'],
ignores: [
'**/node_modules/**',
'**/dist/**',
...NEVER_LINTED,
// First cut is non-test code (08-03 triage). The ratchet lifts this and
// holds the test residual to a count instead.
...QUERY_OPTIONS_TEST_GLOBS,
Expand DownExpand Up@@ -1178,7 +1210,7 @@ export default [
// fakes — the arm the guard is worth the most on.
{
files: ['**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/.turbo/**'],
ignores: [...NEVER_LINTED],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand All@@ -1204,7 +1236,7 @@ export default [
// stopped being locked.
{
files: COMMENT_SWALLOW_FILES,
ignores: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/.turbo/**'],
ignores: [...NEVER_LINTED],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 57 additions & 25 deletions eslint.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -886,6 +886,32 @@ const commentSwallowPlugin = {
},
};

// ───────────────────────────────────────────────────────────────────────────
// The trees nothing in this repo ever lints: installed dependencies and build
// output. Named ONCE and spread into the global ignore below AND into every
// per-object `ignores` beside it, so "no config object can match build output"
// is a property of this one array instead of seven lists that happen to agree.
//
// They did not agree. Four of the seven `files` objects below omitted
// `**/build/**`, `**/.next/**` and `**/.turbo/**`, so TypeScript under those
// directories resolved to three ENABLED rules while the same generated file
// under `dist/` resolved to none — the incidental-neutrality the levelling in
// #12334 replaced.
//
// ⚠️ The global object alone already keeps these paths out of the linted
// population, so the per-object copies change no behaviour today. They are not
// redundancy: they are what makes the invariant survive a future edit to the
// global object by someone who has never read this history — which is exactly
// how a rule came to be enabled on generated code in the first place.
// ───────────────────────────────────────────────────────────────────────────
const NEVER_LINTED = Object.freeze([
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/.next/**',
'**/.turbo/**',
]);

export default [
// ───────────────────────────────────────────────────────────────────────────
// GLOBAL ignore. This is the ONLY object in this array with no `files` key,
Expand All@@ -910,34 +936,41 @@ export default [
// So the accept/reject semantics of `**/dist/**` do not move: nothing there
// had a rule enabled to lose. The saving is pure enumeration + parse.
//
// ⚠️ SCOPE IS DELIBERATELY NARROWER THAN THE PER-OBJECT LISTS BELOW. Those
// also carry `**/build/**`, `**/.next/**` and `**/.turbo/**`, and promoting
// those three here would NOT be semantics-neutral. Three of the objects
// below (the `packages/**` and `examples/**` ones) list only
// `**/node_modules/**` and `**/dist/**` in their `ignores`, so they still
// match TypeScript under the other three directories:
// ⚠️ THE OTHER THREE PATTERNS ARE A NAMED CORRECTION, NOT A NEUTRAL ONE.
// `**/build/**`, `**/.next/**` and `**/.turbo/**` join this object under a
// maintainer ruling (2026-08-26, option C on #12334, verbatim 「同意」).
// Three rules are enabled on TypeScript under those directories today, and
// promoting the patterns DELIBERATELY TURNS THEM OFF on build output:
//
// packages/core/build/x.ts → LINTED, 3 rules ENABLED
// packages/core/build/x.ts before → LINTED, 3 rules ENABLED
// (slot-lookup/no-any-assignment, no-restricted-syntax,
// query-options/no-any-erasure), parser @typescript-eslint/parser
// after → no config object matches: not linted
//
// Recorded in those words rather than under a "changes nothing" banner,
// because switching a rule off on generated code and switching it off on
// source are the same line in a diff. It repairs this config's own stated
// intent — never lint build output: nobody authors the code under those
// directories, and linting generated code is the defect being removed, not
// coverage being cut.
//
// No such path exists in this repo today — nothing emits to `build/`, and the
// only Next.js app is `apps/docs`, which is outside `packages/**` — so this
// is latent rather than live. It is still a real semantic change, and it is
// left out of this object pending a maintainer ruling rather than taken
// silently under a "changes nothing" banner. See #12304.
// `**/.next/**` is LIVE, not latent (measured 2026-08-25 on #12334):
// `apps/docs/.next` is 3.9 GB after `pnpm --filter @objectstack/docs build`,
// and linting it is a hard OOM — `exit 134`, "Ineffective mark-compacts near
// heap limit", 66 s, no eslint output — which reads as a crashed gate rather
// than as 3.9 GB of chunks being parsed. A tracked-file census cannot see it:
// `.next` is untracked output, and `git ls-files` matches 0 files under any
// of the five patterns (control: 4,776 under `/src/`). `build/` and
// `.turbo/` stay latent on that census; the levelling below is what stops
// either of them becoming the next `.next`. See #12334 / #12304.
// ───────────────────────────────────────────────────────────────────────────
{
ignores: ['**/node_modules/**', '**/dist/**'],
ignores: [...NEVER_LINTED],
},
{
files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'],
ignores: [
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/.next/**',
'**/.turbo/**',
...NEVER_LINTED,
'packages/spec/**',
// CLI/scaffold templates contain `@objectstack/spec` strings that are
// emitted to user projects, not actual imports in this repo.
Expand DownExpand Up@@ -980,7 +1013,7 @@ export default [
// reason to suspect it.
{
files: ['packages/cli/src/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', '**/*.test.ts'],
ignores: [...NEVER_LINTED, '**/*.test.ts'],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1019,7 +1052,7 @@ export default [
// backward-compat fixture (#2089) and are intentional.
{
files: ['examples/**/*.{ts,tsx,mts,cts}', 'packages/apps/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', 'packages/qa/downstream-contract/**'],
ignores: [...NEVER_LINTED, 'packages/qa/downstream-contract/**'],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1068,7 +1101,7 @@ export default [
// needs type information, so it belongs to a typed-lint pass, not here.
{
files: ['packages/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', ...SLOT_LOOKUP_UNSWEPT],
ignores: [...NEVER_LINTED, ...SLOT_LOOKUP_UNSWEPT],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1138,8 +1171,7 @@ export default [
{
files: ['packages/**/*.{ts,tsx,mts,cts}'],
ignores: [
'**/node_modules/**',
'**/dist/**',
...NEVER_LINTED,
// First cut is non-test code (08-03 triage). The ratchet lifts this and
// holds the test residual to a count instead.
...QUERY_OPTIONS_TEST_GLOBS,
Expand DownExpand Up@@ -1178,7 +1210,7 @@ export default [
// fakes — the arm the guard is worth the most on.
{
files: ['**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/.turbo/**'],
ignores: [...NEVER_LINTED],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand All@@ -1204,7 +1236,7 @@ export default [
// stopped being locked.
{
files: COMMENT_SWALLOW_FILES,
ignores: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/.turbo/**'],
ignores: [...NEVER_LINTED],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 57 additions & 25 deletions eslint.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -886,6 +886,32 @@ const commentSwallowPlugin = {
},
};

// ───────────────────────────────────────────────────────────────────────────
// The trees nothing in this repo ever lints: installed dependencies and build
// output. Named ONCE and spread into the global ignore below AND into every
// per-object `ignores` beside it, so "no config object can match build output"
// is a property of this one array instead of seven lists that happen to agree.
//
// They did not agree. Four of the seven `files` objects below omitted
// `**/build/**`, `**/.next/**` and `**/.turbo/**`, so TypeScript under those
// directories resolved to three ENABLED rules while the same generated file
// under `dist/` resolved to none — the incidental-neutrality the levelling in
// #12334 replaced.
//
// ⚠️ The global object alone already keeps these paths out of the linted
// population, so the per-object copies change no behaviour today. They are not
// redundancy: they are what makes the invariant survive a future edit to the
// global object by someone who has never read this history — which is exactly
// how a rule came to be enabled on generated code in the first place.
// ───────────────────────────────────────────────────────────────────────────
const NEVER_LINTED = Object.freeze([
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/.next/**',
'**/.turbo/**',
]);

export default [
// ───────────────────────────────────────────────────────────────────────────
// GLOBAL ignore. This is the ONLY object in this array with no `files` key,
Expand All@@ -910,34 +936,41 @@ export default [
// So the accept/reject semantics of `**/dist/**` do not move: nothing there
// had a rule enabled to lose. The saving is pure enumeration + parse.
//
// ⚠️ SCOPE IS DELIBERATELY NARROWER THAN THE PER-OBJECT LISTS BELOW. Those
// also carry `**/build/**`, `**/.next/**` and `**/.turbo/**`, and promoting
// those three here would NOT be semantics-neutral. Three of the objects
// below (the `packages/**` and `examples/**` ones) list only
// `**/node_modules/**` and `**/dist/**` in their `ignores`, so they still
// match TypeScript under the other three directories:
// ⚠️ THE OTHER THREE PATTERNS ARE A NAMED CORRECTION, NOT A NEUTRAL ONE.
// `**/build/**`, `**/.next/**` and `**/.turbo/**` join this object under a
// maintainer ruling (2026-08-26, option C on #12334, verbatim 「同意」).
// Three rules are enabled on TypeScript under those directories today, and
// promoting the patterns DELIBERATELY TURNS THEM OFF on build output:
//
// packages/core/build/x.ts → LINTED, 3 rules ENABLED
// packages/core/build/x.ts before → LINTED, 3 rules ENABLED
// (slot-lookup/no-any-assignment, no-restricted-syntax,
// query-options/no-any-erasure), parser @typescript-eslint/parser
// after → no config object matches: not linted
//
// Recorded in those words rather than under a "changes nothing" banner,
// because switching a rule off on generated code and switching it off on
// source are the same line in a diff. It repairs this config's own stated
// intent — never lint build output: nobody authors the code under those
// directories, and linting generated code is the defect being removed, not
// coverage being cut.
//
// No such path exists in this repo today — nothing emits to `build/`, and the
// only Next.js app is `apps/docs`, which is outside `packages/**` — so this
// is latent rather than live. It is still a real semantic change, and it is
// left out of this object pending a maintainer ruling rather than taken
// silently under a "changes nothing" banner. See #12304.
// `**/.next/**` is LIVE, not latent (measured 2026-08-25 on #12334):
// `apps/docs/.next` is 3.9 GB after `pnpm --filter @objectstack/docs build`,
// and linting it is a hard OOM — `exit 134`, "Ineffective mark-compacts near
// heap limit", 66 s, no eslint output — which reads as a crashed gate rather
// than as 3.9 GB of chunks being parsed. A tracked-file census cannot see it:
// `.next` is untracked output, and `git ls-files` matches 0 files under any
// of the five patterns (control: 4,776 under `/src/`). `build/` and
// `.turbo/` stay latent on that census; the levelling below is what stops
// either of them becoming the next `.next`. See #12334 / #12304.
// ───────────────────────────────────────────────────────────────────────────
{
ignores: ['**/node_modules/**', '**/dist/**'],
ignores: [...NEVER_LINTED],
},
{
files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'],
ignores: [
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/.next/**',
'**/.turbo/**',
...NEVER_LINTED,
'packages/spec/**',
// CLI/scaffold templates contain `@objectstack/spec` strings that are
// emitted to user projects, not actual imports in this repo.
Expand DownExpand Up@@ -980,7 +1013,7 @@ export default [
// reason to suspect it.
{
files: ['packages/cli/src/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', '**/*.test.ts'],
ignores: [...NEVER_LINTED, '**/*.test.ts'],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1019,7 +1052,7 @@ export default [
// backward-compat fixture (#2089) and are intentional.
{
files: ['examples/**/*.{ts,tsx,mts,cts}', 'packages/apps/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', 'packages/qa/downstream-contract/**'],
ignores: [...NEVER_LINTED, 'packages/qa/downstream-contract/**'],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1068,7 +1101,7 @@ export default [
// needs type information, so it belongs to a typed-lint pass, not here.
{
files: ['packages/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', ...SLOT_LOOKUP_UNSWEPT],
ignores: [...NEVER_LINTED, ...SLOT_LOOKUP_UNSWEPT],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1138,8 +1171,7 @@ export default [
{
files: ['packages/**/*.{ts,tsx,mts,cts}'],
ignores: [
'**/node_modules/**',
'**/dist/**',
...NEVER_LINTED,
// First cut is non-test code (08-03 triage). The ratchet lifts this and
// holds the test residual to a count instead.
...QUERY_OPTIONS_TEST_GLOBS,
Expand DownExpand Up@@ -1178,7 +1210,7 @@ export default [
// fakes — the arm the guard is worth the most on.
{
files: ['**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/.turbo/**'],
ignores: [...NEVER_LINTED],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand All@@ -1204,7 +1236,7 @@ export default [
// stopped being locked.
{
files: COMMENT_SWALLOW_FILES,
ignores: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/.turbo/**'],
ignores: [...NEVER_LINTED],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 57 additions & 25 deletions eslint.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -886,6 +886,32 @@ const commentSwallowPlugin = {
},
};

// ───────────────────────────────────────────────────────────────────────────
// The trees nothing in this repo ever lints: installed dependencies and build
// output. Named ONCE and spread into the global ignore below AND into every
// per-object `ignores` beside it, so "no config object can match build output"
// is a property of this one array instead of seven lists that happen to agree.
//
// They did not agree. Four of the seven `files` objects below omitted
// `**/build/**`, `**/.next/**` and `**/.turbo/**`, so TypeScript under those
// directories resolved to three ENABLED rules while the same generated file
// under `dist/` resolved to none — the incidental-neutrality the levelling in
// #12334 replaced.
//
// ⚠️ The global object alone already keeps these paths out of the linted
// population, so the per-object copies change no behaviour today. They are not
// redundancy: they are what makes the invariant survive a future edit to the
// global object by someone who has never read this history — which is exactly
// how a rule came to be enabled on generated code in the first place.
// ───────────────────────────────────────────────────────────────────────────
const NEVER_LINTED = Object.freeze([
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/.next/**',
'**/.turbo/**',
]);

export default [
// ───────────────────────────────────────────────────────────────────────────
// GLOBAL ignore. This is the ONLY object in this array with no `files` key,
Expand All@@ -910,34 +936,41 @@ export default [
// So the accept/reject semantics of `**/dist/**` do not move: nothing there
// had a rule enabled to lose. The saving is pure enumeration + parse.
//
// ⚠️ SCOPE IS DELIBERATELY NARROWER THAN THE PER-OBJECT LISTS BELOW. Those
// also carry `**/build/**`, `**/.next/**` and `**/.turbo/**`, and promoting
// those three here would NOT be semantics-neutral. Three of the objects
// below (the `packages/**` and `examples/**` ones) list only
// `**/node_modules/**` and `**/dist/**` in their `ignores`, so they still
// match TypeScript under the other three directories:
// ⚠️ THE OTHER THREE PATTERNS ARE A NAMED CORRECTION, NOT A NEUTRAL ONE.
// `**/build/**`, `**/.next/**` and `**/.turbo/**` join this object under a
// maintainer ruling (2026-08-26, option C on #12334, verbatim 「同意」).
// Three rules are enabled on TypeScript under those directories today, and
// promoting the patterns DELIBERATELY TURNS THEM OFF on build output:
//
// packages/core/build/x.ts → LINTED, 3 rules ENABLED
// packages/core/build/x.ts before → LINTED, 3 rules ENABLED
// (slot-lookup/no-any-assignment, no-restricted-syntax,
// query-options/no-any-erasure), parser @typescript-eslint/parser
// after → no config object matches: not linted
//
// Recorded in those words rather than under a "changes nothing" banner,
// because switching a rule off on generated code and switching it off on
// source are the same line in a diff. It repairs this config's own stated
// intent — never lint build output: nobody authors the code under those
// directories, and linting generated code is the defect being removed, not
// coverage being cut.
//
// No such path exists in this repo today — nothing emits to `build/`, and the
// only Next.js app is `apps/docs`, which is outside `packages/**` — so this
// is latent rather than live. It is still a real semantic change, and it is
// left out of this object pending a maintainer ruling rather than taken
// silently under a "changes nothing" banner. See #12304.
// `**/.next/**` is LIVE, not latent (measured 2026-08-25 on #12334):
// `apps/docs/.next` is 3.9 GB after `pnpm --filter @objectstack/docs build`,
// and linting it is a hard OOM — `exit 134`, "Ineffective mark-compacts near
// heap limit", 66 s, no eslint output — which reads as a crashed gate rather
// than as 3.9 GB of chunks being parsed. A tracked-file census cannot see it:
// `.next` is untracked output, and `git ls-files` matches 0 files under any
// of the five patterns (control: 4,776 under `/src/`). `build/` and
// `.turbo/` stay latent on that census; the levelling below is what stops
// either of them becoming the next `.next`. See #12334 / #12304.
// ───────────────────────────────────────────────────────────────────────────
{
ignores: ['**/node_modules/**', '**/dist/**'],
ignores: [...NEVER_LINTED],
},
{
files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'],
ignores: [
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/.next/**',
'**/.turbo/**',
...NEVER_LINTED,
'packages/spec/**',
// CLI/scaffold templates contain `@objectstack/spec` strings that are
// emitted to user projects, not actual imports in this repo.
Expand DownExpand Up@@ -980,7 +1013,7 @@ export default [
// reason to suspect it.
{
files: ['packages/cli/src/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', '**/*.test.ts'],
ignores: [...NEVER_LINTED, '**/*.test.ts'],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1019,7 +1052,7 @@ export default [
// backward-compat fixture (#2089) and are intentional.
{
files: ['examples/**/*.{ts,tsx,mts,cts}', 'packages/apps/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', 'packages/qa/downstream-contract/**'],
ignores: [...NEVER_LINTED, 'packages/qa/downstream-contract/**'],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1068,7 +1101,7 @@ export default [
// needs type information, so it belongs to a typed-lint pass, not here.
{
files: ['packages/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', ...SLOT_LOOKUP_UNSWEPT],
ignores: [...NEVER_LINTED, ...SLOT_LOOKUP_UNSWEPT],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1138,8 +1171,7 @@ export default [
{
files: ['packages/**/*.{ts,tsx,mts,cts}'],
ignores: [
'**/node_modules/**',
'**/dist/**',
...NEVER_LINTED,
// First cut is non-test code (08-03 triage). The ratchet lifts this and
// holds the test residual to a count instead.
...QUERY_OPTIONS_TEST_GLOBS,
Expand DownExpand Up@@ -1178,7 +1210,7 @@ export default [
// fakes — the arm the guard is worth the most on.
{
files: ['**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/.turbo/**'],
ignores: [...NEVER_LINTED],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand All@@ -1204,7 +1236,7 @@ export default [
// stopped being locked.
{
files: COMMENT_SWALLOW_FILES,
ignores: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/.turbo/**'],
ignores: [...NEVER_LINTED],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 57 additions & 25 deletions eslint.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -886,6 +886,32 @@ const commentSwallowPlugin = {
},
};

// ───────────────────────────────────────────────────────────────────────────
// The trees nothing in this repo ever lints: installed dependencies and build
// output. Named ONCE and spread into the global ignore below AND into every
// per-object `ignores` beside it, so "no config object can match build output"
// is a property of this one array instead of seven lists that happen to agree.
//
// They did not agree. Four of the seven `files` objects below omitted
// `**/build/**`, `**/.next/**` and `**/.turbo/**`, so TypeScript under those
// directories resolved to three ENABLED rules while the same generated file
// under `dist/` resolved to none — the incidental-neutrality the levelling in
// #12334 replaced.
//
// ⚠️ The global object alone already keeps these paths out of the linted
// population, so the per-object copies change no behaviour today. They are not
// redundancy: they are what makes the invariant survive a future edit to the
// global object by someone who has never read this history — which is exactly
// how a rule came to be enabled on generated code in the first place.
// ───────────────────────────────────────────────────────────────────────────
const NEVER_LINTED = Object.freeze([
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/.next/**',
'**/.turbo/**',
]);

export default [
// ───────────────────────────────────────────────────────────────────────────
// GLOBAL ignore. This is the ONLY object in this array with no `files` key,
Expand All@@ -910,34 +936,41 @@ export default [
// So the accept/reject semantics of `**/dist/**` do not move: nothing there
// had a rule enabled to lose. The saving is pure enumeration + parse.
//
// ⚠️ SCOPE IS DELIBERATELY NARROWER THAN THE PER-OBJECT LISTS BELOW. Those
// also carry `**/build/**`, `**/.next/**` and `**/.turbo/**`, and promoting
// those three here would NOT be semantics-neutral. Three of the objects
// below (the `packages/**` and `examples/**` ones) list only
// `**/node_modules/**` and `**/dist/**` in their `ignores`, so they still
// match TypeScript under the other three directories:
// ⚠️ THE OTHER THREE PATTERNS ARE A NAMED CORRECTION, NOT A NEUTRAL ONE.
// `**/build/**`, `**/.next/**` and `**/.turbo/**` join this object under a
// maintainer ruling (2026-08-26, option C on #12334, verbatim 「同意」).
// Three rules are enabled on TypeScript under those directories today, and
// promoting the patterns DELIBERATELY TURNS THEM OFF on build output:
//
// packages/core/build/x.ts → LINTED, 3 rules ENABLED
// packages/core/build/x.ts before → LINTED, 3 rules ENABLED
// (slot-lookup/no-any-assignment, no-restricted-syntax,
// query-options/no-any-erasure), parser @typescript-eslint/parser
// after → no config object matches: not linted
//
// Recorded in those words rather than under a "changes nothing" banner,
// because switching a rule off on generated code and switching it off on
// source are the same line in a diff. It repairs this config's own stated
// intent — never lint build output: nobody authors the code under those
// directories, and linting generated code is the defect being removed, not
// coverage being cut.
//
// No such path exists in this repo today — nothing emits to `build/`, and the
// only Next.js app is `apps/docs`, which is outside `packages/**` — so this
// is latent rather than live. It is still a real semantic change, and it is
// left out of this object pending a maintainer ruling rather than taken
// silently under a "changes nothing" banner. See #12304.
// `**/.next/**` is LIVE, not latent (measured 2026-08-25 on #12334):
// `apps/docs/.next` is 3.9 GB after `pnpm --filter @objectstack/docs build`,
// and linting it is a hard OOM — `exit 134`, "Ineffective mark-compacts near
// heap limit", 66 s, no eslint output — which reads as a crashed gate rather
// than as 3.9 GB of chunks being parsed. A tracked-file census cannot see it:
// `.next` is untracked output, and `git ls-files` matches 0 files under any
// of the five patterns (control: 4,776 under `/src/`). `build/` and
// `.turbo/` stay latent on that census; the levelling below is what stops
// either of them becoming the next `.next`. See #12334 / #12304.
// ───────────────────────────────────────────────────────────────────────────
{
ignores: ['**/node_modules/**', '**/dist/**'],
ignores: [...NEVER_LINTED],
},
{
files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'],
ignores: [
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/.next/**',
'**/.turbo/**',
...NEVER_LINTED,
'packages/spec/**',
// CLI/scaffold templates contain `@objectstack/spec` strings that are
// emitted to user projects, not actual imports in this repo.
Expand DownExpand Up@@ -980,7 +1013,7 @@ export default [
// reason to suspect it.
{
files: ['packages/cli/src/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', '**/*.test.ts'],
ignores: [...NEVER_LINTED, '**/*.test.ts'],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1019,7 +1052,7 @@ export default [
// backward-compat fixture (#2089) and are intentional.
{
files: ['examples/**/*.{ts,tsx,mts,cts}', 'packages/apps/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', 'packages/qa/downstream-contract/**'],
ignores: [...NEVER_LINTED, 'packages/qa/downstream-contract/**'],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1068,7 +1101,7 @@ export default [
// needs type information, so it belongs to a typed-lint pass, not here.
{
files: ['packages/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', ...SLOT_LOOKUP_UNSWEPT],
ignores: [...NEVER_LINTED, ...SLOT_LOOKUP_UNSWEPT],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1138,8 +1171,7 @@ export default [
{
files: ['packages/**/*.{ts,tsx,mts,cts}'],
ignores: [
'**/node_modules/**',
'**/dist/**',
...NEVER_LINTED,
// First cut is non-test code (08-03 triage). The ratchet lifts this and
// holds the test residual to a count instead.
...QUERY_OPTIONS_TEST_GLOBS,
Expand DownExpand Up@@ -1178,7 +1210,7 @@ export default [
// fakes — the arm the guard is worth the most on.
{
files: ['**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/.turbo/**'],
ignores: [...NEVER_LINTED],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand All@@ -1204,7 +1236,7 @@ export default [
// stopped being locked.
{
files: COMMENT_SWALLOW_FILES,
ignores: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/.turbo/**'],
ignores: [...NEVER_LINTED],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 57 additions & 25 deletions eslint.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -886,6 +886,32 @@ const commentSwallowPlugin = {
},
};

// ───────────────────────────────────────────────────────────────────────────
// The trees nothing in this repo ever lints: installed dependencies and build
// output. Named ONCE and spread into the global ignore below AND into every
// per-object `ignores` beside it, so "no config object can match build output"
// is a property of this one array instead of seven lists that happen to agree.
//
// They did not agree. Four of the seven `files` objects below omitted
// `**/build/**`, `**/.next/**` and `**/.turbo/**`, so TypeScript under those
// directories resolved to three ENABLED rules while the same generated file
// under `dist/` resolved to none — the incidental-neutrality the levelling in
// #12334 replaced.
//
// ⚠️ The global object alone already keeps these paths out of the linted
// population, so the per-object copies change no behaviour today. They are not
// redundancy: they are what makes the invariant survive a future edit to the
// global object by someone who has never read this history — which is exactly
// how a rule came to be enabled on generated code in the first place.
// ───────────────────────────────────────────────────────────────────────────
const NEVER_LINTED = Object.freeze([
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/.next/**',
'**/.turbo/**',
]);

export default [
// ───────────────────────────────────────────────────────────────────────────
// GLOBAL ignore. This is the ONLY object in this array with no `files` key,
Expand All@@ -910,34 +936,41 @@ export default [
// So the accept/reject semantics of `**/dist/**` do not move: nothing there
// had a rule enabled to lose. The saving is pure enumeration + parse.
//
// ⚠️ SCOPE IS DELIBERATELY NARROWER THAN THE PER-OBJECT LISTS BELOW. Those
// also carry `**/build/**`, `**/.next/**` and `**/.turbo/**`, and promoting
// those three here would NOT be semantics-neutral. Three of the objects
// below (the `packages/**` and `examples/**` ones) list only
// `**/node_modules/**` and `**/dist/**` in their `ignores`, so they still
// match TypeScript under the other three directories:
// ⚠️ THE OTHER THREE PATTERNS ARE A NAMED CORRECTION, NOT A NEUTRAL ONE.
// `**/build/**`, `**/.next/**` and `**/.turbo/**` join this object under a
// maintainer ruling (2026-08-26, option C on #12334, verbatim 「同意」).
// Three rules are enabled on TypeScript under those directories today, and
// promoting the patterns DELIBERATELY TURNS THEM OFF on build output:
//
// packages/core/build/x.ts → LINTED, 3 rules ENABLED
// packages/core/build/x.ts before → LINTED, 3 rules ENABLED
// (slot-lookup/no-any-assignment, no-restricted-syntax,
// query-options/no-any-erasure), parser @typescript-eslint/parser
// after → no config object matches: not linted
//
// Recorded in those words rather than under a "changes nothing" banner,
// because switching a rule off on generated code and switching it off on
// source are the same line in a diff. It repairs this config's own stated
// intent — never lint build output: nobody authors the code under those
// directories, and linting generated code is the defect being removed, not
// coverage being cut.
//
// No such path exists in this repo today — nothing emits to `build/`, and the
// only Next.js app is `apps/docs`, which is outside `packages/**` — so this
// is latent rather than live. It is still a real semantic change, and it is
// left out of this object pending a maintainer ruling rather than taken
// silently under a "changes nothing" banner. See #12304.
// `**/.next/**` is LIVE, not latent (measured 2026-08-25 on #12334):
// `apps/docs/.next` is 3.9 GB after `pnpm --filter @objectstack/docs build`,
// and linting it is a hard OOM — `exit 134`, "Ineffective mark-compacts near
// heap limit", 66 s, no eslint output — which reads as a crashed gate rather
// than as 3.9 GB of chunks being parsed. A tracked-file census cannot see it:
// `.next` is untracked output, and `git ls-files` matches 0 files under any
// of the five patterns (control: 4,776 under `/src/`). `build/` and
// `.turbo/` stay latent on that census; the levelling below is what stops
// either of them becoming the next `.next`. See #12334 / #12304.
// ───────────────────────────────────────────────────────────────────────────
{
ignores: ['**/node_modules/**', '**/dist/**'],
ignores: [...NEVER_LINTED],
},
{
files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'],
ignores: [
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/.next/**',
'**/.turbo/**',
...NEVER_LINTED,
'packages/spec/**',
// CLI/scaffold templates contain `@objectstack/spec` strings that are
// emitted to user projects, not actual imports in this repo.
Expand DownExpand Up@@ -980,7 +1013,7 @@ export default [
// reason to suspect it.
{
files: ['packages/cli/src/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', '**/*.test.ts'],
ignores: [...NEVER_LINTED, '**/*.test.ts'],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1019,7 +1052,7 @@ export default [
// backward-compat fixture (#2089) and are intentional.
{
files: ['examples/**/*.{ts,tsx,mts,cts}', 'packages/apps/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', 'packages/qa/downstream-contract/**'],
ignores: [...NEVER_LINTED, 'packages/qa/downstream-contract/**'],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1068,7 +1101,7 @@ export default [
// needs type information, so it belongs to a typed-lint pass, not here.
{
files: ['packages/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', ...SLOT_LOOKUP_UNSWEPT],
ignores: [...NEVER_LINTED, ...SLOT_LOOKUP_UNSWEPT],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand DownExpand Up@@ -1138,8 +1171,7 @@ export default [
{
files: ['packages/**/*.{ts,tsx,mts,cts}'],
ignores: [
'**/node_modules/**',
'**/dist/**',
...NEVER_LINTED,
// First cut is non-test code (08-03 triage). The ratchet lifts this and
// holds the test residual to a count instead.
...QUERY_OPTIONS_TEST_GLOBS,
Expand DownExpand Up@@ -1178,7 +1210,7 @@ export default [
// fakes — the arm the guard is worth the most on.
{
files: ['**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/.turbo/**'],
ignores: [...NEVER_LINTED],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand All@@ -1204,7 +1236,7 @@ export default [
// stopped being locked.
{
files: COMMENT_SWALLOW_FILES,
ignores: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/.turbo/**'],
ignores: [...NEVER_LINTED],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand Down
Loading