From b3d2de87f9e7b30ef352a27c1ce6f1cd94c26363 Mon Sep 17 00:00:00 2001 From: alt-glitch Date: Tue, 9 Jun 2026 07:58:50 +0000 Subject: [PATCH] opentui(ts): type-aware eslint (projectService + recommendedTypeChecked); defer cast-family to warn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable type-aware linting in ui-tui-opentui-v2: add projectService + tsconfigRootDir to the TS files block and switch the preset to recommendedTypeChecked. Turn ON as ERROR the high-value promise rules (no-floating-promises, no-misused-promises, await-thenable) and fix the 3 real floating-promise sites in the gateway client (FileSink write/flush/end are fire-and-forget on a piped child stdin — marked with explicit `void`). Defer the cast/unknown family + the noisy type-checked rules to 'warn' (gate stays green; eslint exits 0 on warnings) for Phase 2, which will replace the `as`/`unknown` boundary casts with Schema decoding: no-unsafe-{assignment,member-access,argument,return,call}, no-unnecessary-condition, no-base-to-string, restrict-template- expressions, no-unnecessary-type-assertion, require-await. --- ui-tui-opentui-v2/eslint.config.mjs | 39 ++++++++++++++++++- .../src/boundary/gateway/client.ts | 10 +++-- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/ui-tui-opentui-v2/eslint.config.mjs b/ui-tui-opentui-v2/eslint.config.mjs index 9302c0ef2e9..88287cc2234 100644 --- a/ui-tui-opentui-v2/eslint.config.mjs +++ b/ui-tui-opentui-v2/eslint.config.mjs @@ -7,9 +7,15 @@ export default tseslint.config( ignores: ["node_modules/**", "dist/**", ".repos/**", "*.frame.txt", "*.ansi"], }, js.configs.recommended, - ...tseslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, { files: ["**/*.ts", "**/*.tsx"], + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, plugins: { "unused-imports": unusedImports, }, @@ -23,6 +29,37 @@ export default tseslint.config( "warn", { vars: "all", varsIgnorePattern: "^_", args: "after-used", argsIgnorePattern: "^_" }, ], + + // --- Type-aware, high-value: ON as ERROR --- + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-misused-promises": "error", + "@typescript-eslint/await-thenable": "error", + + // --- Phase 2: promote to error after Schema decode replaces the casts --- + // The cast/`unknown` family fires on the `as`/`unknown` boundary code that + // Phase 2 will replace with Schema decoding. Deferred to 'warn' so the gate + // stays green (eslint exits 0 on warnings) without masking the real signal. + "@typescript-eslint/no-unsafe-assignment": "warn", + "@typescript-eslint/no-unsafe-member-access": "warn", + "@typescript-eslint/no-unsafe-argument": "warn", + "@typescript-eslint/no-unsafe-return": "warn", + "@typescript-eslint/no-unsafe-call": "warn", + "@typescript-eslint/no-unnecessary-condition": "warn", + "@typescript-eslint/no-base-to-string": "warn", + "@typescript-eslint/restrict-template-expressions": "warn", + // `no-unnecessary-type-assertion` is the cast family (the one site is a + // `effect as Effect.Effect<…>` test cast); `require-await` fires only on + // async test-mock fns satisfying an async signature. Both are recommended + // -TypeChecked errors by default — demote to 'warn' to keep the gate green + // until Phase 2 (Schema decode) removes the casts. + "@typescript-eslint/no-unnecessary-type-assertion": "warn", + "@typescript-eslint/require-await": "warn", }, }, + { + // The eslint flat config is JS-only; the typed parser project service does not + // cover it, so disable type-checking there to avoid parser errors. + files: ["eslint.config.mjs"], + ...tseslint.configs.disableTypeChecked, + }, ) diff --git a/ui-tui-opentui-v2/src/boundary/gateway/client.ts b/ui-tui-opentui-v2/src/boundary/gateway/client.ts index 7ddc300c42d..dcb2dbbef91 100644 --- a/ui-tui-opentui-v2/src/boundary/gateway/client.ts +++ b/ui-tui-opentui-v2/src/boundary/gateway/client.ts @@ -227,8 +227,11 @@ export class RawGatewayClient { }) try { - stdin.write(frame) - stdin.flush() + // FileSink.write/flush type as `number | Promise`; for a piped + // child stdin they return synchronously — this is intentional + // fire-and-forget (no backpressure await), so mark the intent explicitly. + void stdin.write(frame) + void stdin.flush() } catch (cause) { this.pending.delete(id) clearTimeout(timer) @@ -250,7 +253,8 @@ export class RawGatewayClient { const stdin = this.proc?.stdin if (stdin && typeof stdin !== 'number') { try { - stdin.end() + // Fire-and-forget EOF: FileSink.end types as `number | Promise`. + void stdin.end() } catch { // already gone }