From 72cf8be40d81e2e4ae84d924bc114299d7568874 Mon Sep 17 00:00:00 2001 From: Ivan Banov Date: Tue, 18 Aug 2026 11:39:39 +0200 Subject: [PATCH 1/5] chore: give each package its own tsconfig, drop the root tsconfig folder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tsconfig/react.json actually typechecked everything except solid, while the solid project already lived in its package — an inconsistent split. Now the root tsconfig.json is the shared base (it also covers the root config files), tsconfig.build.json is the tsc -b solution list, and every package owns its project config. Only react and solid set a JSX flavor. Co-Authored-By: Claude Fable 5 --- package.json | 2 +- packages/core/tsconfig.json | 5 +++++ packages/native/tsconfig.json | 5 +++++ packages/opentui/tsconfig.json | 5 +++++ packages/react/tsconfig.json | 8 +++++++ packages/shared/bindings/tsconfig.json | 5 +++++ packages/shared/utils/tsconfig.json | 5 +++++ packages/solid/tsconfig.json | 6 ++---- tsconfig.build.json | 16 ++++++++++++++ tsconfig.json | 29 +++++++++++++++++++++++++- tsconfig/all.json | 8 ------- tsconfig/base.json | 26 ----------------------- tsconfig/react.json | 8 ------- 13 files changed, 80 insertions(+), 48 deletions(-) create mode 100644 packages/core/tsconfig.json create mode 100644 packages/native/tsconfig.json create mode 100644 packages/opentui/tsconfig.json create mode 100644 packages/react/tsconfig.json create mode 100644 packages/shared/bindings/tsconfig.json create mode 100644 packages/shared/utils/tsconfig.json create mode 100644 tsconfig.build.json delete mode 100644 tsconfig/all.json delete mode 100644 tsconfig/base.json delete mode 100644 tsconfig/react.json diff --git a/package.json b/package.json index 1d8bb7a..93b76fe 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "website:dev": "pnpm -C website dev", "website:prod": "pnpm -C website build", "build": "tsdown", - "typecheck": "tsc -b tsconfig/all.json", + "typecheck": "tsc -b tsconfig.build.json", "lint": "oxlint .", "format": "oxfmt .", "format:check": "oxfmt --check .", diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 0000000..3fd6989 --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src", "tests"], + "exclude": ["**/node_modules", "**/dist"] +} diff --git a/packages/native/tsconfig.json b/packages/native/tsconfig.json new file mode 100644 index 0000000..3fd6989 --- /dev/null +++ b/packages/native/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src", "tests"], + "exclude": ["**/node_modules", "**/dist"] +} diff --git a/packages/opentui/tsconfig.json b/packages/opentui/tsconfig.json new file mode 100644 index 0000000..3fd6989 --- /dev/null +++ b/packages/opentui/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src", "tests"], + "exclude": ["**/node_modules", "**/dist"] +} diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json new file mode 100644 index 0000000..8eb7469 --- /dev/null +++ b/packages/react/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx" + }, + "include": ["src", "tests"], + "exclude": ["**/node_modules", "**/dist"] +} diff --git a/packages/shared/bindings/tsconfig.json b/packages/shared/bindings/tsconfig.json new file mode 100644 index 0000000..a6eae6c --- /dev/null +++ b/packages/shared/bindings/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../../tsconfig.json", + "include": ["src", "tests"], + "exclude": ["**/node_modules", "**/dist"] +} diff --git a/packages/shared/utils/tsconfig.json b/packages/shared/utils/tsconfig.json new file mode 100644 index 0000000..a6eae6c --- /dev/null +++ b/packages/shared/utils/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../../tsconfig.json", + "include": ["src", "tests"], + "exclude": ["**/node_modules", "**/dist"] +} diff --git a/packages/solid/tsconfig.json b/packages/solid/tsconfig.json index 1716ed0..170c486 100644 --- a/packages/solid/tsconfig.json +++ b/packages/solid/tsconfig.json @@ -1,8 +1,6 @@ { - // Referenced from tsconfig/all.json; lives here (like vitest.config.ts) so - // the root workspace carries no Solid dependencies. Solid 2.0 moved the web - // JSX namespace out of `solid-js` into `@solidjs/web`. - "extends": "../../tsconfig/base.json", + // Solid 2.0 moved the web JSX namespace out of `solid-js` into `@solidjs/web`. + "extends": "../../tsconfig.json", "compilerOptions": { "jsx": "preserve", "jsxImportSource": "@solidjs/web" diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..a3cfcaa --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,16 @@ +{ + // `pnpm typecheck` entry: builds every project. Each package owns its + // tsconfig; the "." reference covers the root config files. + "files": [], + "references": [ + { "path": "./tsconfig.json" }, + { "path": "./packages/core" }, + { "path": "./packages/react" }, + { "path": "./packages/solid" }, + { "path": "./packages/native" }, + { "path": "./packages/opentui" }, + { "path": "./packages/shared/utils" }, + { "path": "./packages/shared/bindings" }, + { "path": "./benchmark" } + ] +} diff --git a/tsconfig.json b/tsconfig.json index 967cf8e..a9ff9cd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,3 +1,30 @@ { - "extends": "./tsconfig/base.json" + // The shared base: packages, sandboxes, and the benchmark extend this. Each + // package owns its project tsconfig (referenced from tsconfig.build.json); + // this file's own include covers only the root config files. + "compilerOptions": { + "target": "esnext", + "allowJs": false, + "noEmit": true, + "esModuleInterop": true, + "isolatedModules": true, + "lib": ["dom", "dom.iterable", "esnext"], + "module": "esnext", + "moduleResolution": "bundler", + "noImplicitReturns": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "paths": { + "@dunky.dev/state-machine": ["./packages/core/src"], + "@dunky.dev/react-state-machine": ["./packages/react/src"], + "@dunky.dev/solid-state-machine": ["./packages/solid/src"], + "@dunky.dev/native-state-machine": ["./packages/native/src"], + "@dunky.dev/opentui-state-machine": ["./packages/opentui/src"], + "@dunky.dev/state-machine-utils": ["./packages/shared/utils/src"], + "@dunky.dev/state-machine-bindings": ["./packages/shared/bindings/src"] + }, + "types": ["@types/node", "vitest/globals"] + }, + "include": ["*.ts"] } diff --git a/tsconfig/all.json b/tsconfig/all.json deleted file mode 100644 index 9ee2bb9..0000000 --- a/tsconfig/all.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "files": [], - "references": [ - { "path": "./react.json" }, - { "path": "../packages/solid" }, - { "path": "../benchmark/tsconfig.json" } - ] -} diff --git a/tsconfig/base.json b/tsconfig/base.json deleted file mode 100644 index 49f680f..0000000 --- a/tsconfig/base.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "target": "esnext", - "allowJs": false, - "noEmit": true, - "esModuleInterop": true, - "isolatedModules": true, - "lib": ["dom", "dom.iterable", "esnext"], - "module": "esnext", - "moduleResolution": "bundler", - "noImplicitReturns": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "strict": true, - "paths": { - "@dunky.dev/state-machine": ["../packages/core/src"], - "@dunky.dev/react-state-machine": ["../packages/react/src"], - "@dunky.dev/solid-state-machine": ["../packages/solid/src"], - "@dunky.dev/native-state-machine": ["../packages/native/src"], - "@dunky.dev/opentui-state-machine": ["../packages/opentui/src"], - "@dunky.dev/state-machine-utils": ["../packages/shared/utils/src"], - "@dunky.dev/state-machine-bindings": ["../packages/shared/bindings/src"] - }, - "types": ["@types/node", "vitest/globals"] - } -} diff --git a/tsconfig/react.json b/tsconfig/react.json deleted file mode 100644 index 483885c..0000000 --- a/tsconfig/react.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "./base.json", - "compilerOptions": { - "jsx": "react-jsx" - }, - "include": ["../*.ts", "../packages"], - "exclude": ["../**/node_modules", "../**/dist", "../packages/solid"] -} From 4f7fe333b93f5681fa28924e7904c3d9c8ad264c Mon Sep 17 00:00:00 2001 From: Ivan Banov Date: Tue, 18 Aug 2026 12:32:43 +0200 Subject: [PATCH 2/5] Update tsconfig.json Signed-off-by: Ivan Banov --- tsconfig.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index a9ff9cd..8436124 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,4 @@ { - // The shared base: packages, sandboxes, and the benchmark extend this. Each - // package owns its project tsconfig (referenced from tsconfig.build.json); - // this file's own include covers only the root config files. "compilerOptions": { "target": "esnext", "allowJs": false, From e2200a8856154834d8ac10c7c7e7b3a8156005ef Mon Sep 17 00:00:00 2001 From: Ivan Banov Date: Tue, 18 Aug 2026 12:33:02 +0200 Subject: [PATCH 3/5] Update tsconfig.build.json Signed-off-by: Ivan Banov --- tsconfig.build.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/tsconfig.build.json b/tsconfig.build.json index a3cfcaa..38dcd1a 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,6 +1,4 @@ { - // `pnpm typecheck` entry: builds every project. Each package owns its - // tsconfig; the "." reference covers the root config files. "files": [], "references": [ { "path": "./tsconfig.json" }, From d5698f6d7835711830299bab1b10eba17771eb96 Mon Sep 17 00:00:00 2001 From: Ivan Banov Date: Tue, 18 Aug 2026 12:33:32 +0200 Subject: [PATCH 4/5] Update tsconfig.json Signed-off-by: Ivan Banov --- packages/solid/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/solid/tsconfig.json b/packages/solid/tsconfig.json index 170c486..4aab66b 100644 --- a/packages/solid/tsconfig.json +++ b/packages/solid/tsconfig.json @@ -1,5 +1,4 @@ { - // Solid 2.0 moved the web JSX namespace out of `solid-js` into `@solidjs/web`. "extends": "../../tsconfig.json", "compilerOptions": { "jsx": "preserve", From 9f1340a88eb4c6b14e8ed2e21cd98d4268d5adf4 Mon Sep 17 00:00:00 2001 From: Ivan Banov Date: Tue, 18 Aug 2026 14:38:55 +0200 Subject: [PATCH 5/5] fix(solid): stringify boolean aria-* values in normalize MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Solid 2.0 renders a boolean attribute as presence/absence, so aria-expanded={false} disappeared and aria-modal={true} rendered empty. ARIA states are literal "true"/"false" tokens — serialize them explicitly. Found by the solid-dialog binding in dunky-dev/ui#44. Co-Authored-By: Claude Fable 5 --- packages/solid/src/normalize.ts | 14 +++++++++--- packages/solid/tests/normalize.test.ts | 30 ++++++++++++++------------ 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/packages/solid/src/normalize.ts b/packages/solid/src/normalize.ts index 7ee1930..5058d83 100644 --- a/packages/solid/src/normalize.ts +++ b/packages/solid/src/normalize.ts @@ -111,8 +111,8 @@ export const ATTR_MAP: AttrTargets = { // labeling label: 'aria-label', - // widget state (values pass through untransformed — booleans, the 'mixed' - // tristate, and the aria-current / aria-invalid enums all serialize as-is) + // widget state ('mixed' tristate and the aria-current / aria-invalid enums + // pass through as-is; booleans are stringified in normalize below) checked: 'aria-checked', pressed: 'aria-pressed', current: 'aria-current', @@ -168,7 +168,15 @@ export function normalize(logical: Bindings): Record { const attr = ATTR_MAP[key as AttrKey] if (attr) { - out[attr] = key === 'focusable' ? (value ? 0 : -1) : value + if (key === 'focusable') { + out[attr] = value ? 0 : -1 + } else if (typeof value === 'boolean' && attr.startsWith('aria-')) { + // Solid 2.0 treats a boolean attribute as presence/absence; ARIA + // states are literal "true"/"false" tokens, so serialize explicitly. + out[attr] = String(value) + } else { + out[attr] = value + } continue } diff --git a/packages/solid/tests/normalize.test.ts b/packages/solid/tests/normalize.test.ts index 73fd8f7..7b24953 100644 --- a/packages/solid/tests/normalize.test.ts +++ b/packages/solid/tests/normalize.test.ts @@ -55,18 +55,20 @@ describe('solid normalize — attributes', () => { it('maps hasPopup to aria-haspopup (string or boolean)', () => { expect(normalize({ hasPopup: 'menu' })).toEqual({ 'aria-haspopup': 'menu' }) - expect(normalize({ hasPopup: true })).toEqual({ 'aria-haspopup': true }) + expect(normalize({ hasPopup: true })).toEqual({ 'aria-haspopup': 'true' }) }) - it('maps the boolean state attrs to their aria-* equivalents', () => { + // Booleans stringify: Solid 2.0 renders a boolean attribute as presence/ + // absence, but ARIA states are literal "true"/"false" tokens. + it('maps the boolean state attrs to their aria-* equivalents as string tokens', () => { expect( normalize({ expanded: true, selected: false, disabled: true, hidden: false, modal: true }), ).toEqual({ - 'aria-expanded': true, - 'aria-selected': false, - 'aria-disabled': true, - 'aria-hidden': false, - 'aria-modal': true, + 'aria-expanded': 'true', + 'aria-selected': 'false', + 'aria-disabled': 'true', + 'aria-hidden': 'false', + 'aria-modal': 'true', }) }) @@ -197,12 +199,12 @@ describe('solid normalize — expanded attribute surface', () => { }), ).toEqual({ 'aria-checked': 'mixed', - 'aria-pressed': true, + 'aria-pressed': 'true', 'aria-current': 'page', - 'aria-busy': true, + 'aria-busy': 'true', 'aria-invalid': 'spelling', - 'aria-required': true, - 'aria-readonly': false, + 'aria-required': 'true', + 'aria-readonly': 'false', }) }) @@ -248,8 +250,8 @@ describe('solid normalize — expanded attribute surface', () => { 'aria-orientation': 'horizontal', 'aria-sort': 'ascending', 'aria-autocomplete': 'list', - 'aria-multiline': true, - 'aria-multiselectable': false, + 'aria-multiline': 'true', + 'aria-multiselectable': 'false', 'aria-level': 2, 'aria-posinset': 3, 'aria-setsize': 10, @@ -265,7 +267,7 @@ describe('solid normalize — expanded attribute surface', () => { it('maps live-region attrs (off passes through as aria-live="off")', () => { expect(normalize({ live: 'off', atomic: true })).toEqual({ 'aria-live': 'off', - 'aria-atomic': true, + 'aria-atomic': 'true', }) })