Skip to content

Commit dc7a798

Browse files
os-zhuangclaude
andauthored
fix(plugin-grid,plugin-form,cli,+2): type-check the last five unchecked packages, and fix the two runtime bugs hiding there (#2919) (#2936)
Closes the remaining `DEBT` entries from the #2911 sweep. Each package gains `"type-check": "tsc --noEmit"` and loses its entry in the coverage guard; coverage goes 36 -> 41 of 45, outstanding errors 25 -> 5 (only #2916 and #2918 remain). Two were real bugs, not type noise: - cli: `objectui validate` could never report a validation failure. Zod 4 removed `ZodError.errors`, so it read undefined and `.forEach` threw a TypeError that the enclosing catch reported as "Error reading or parsing schema file" — swallowing the errors the command exists to print. Now reads `.issues`. - plugin-grid: grouping by a boolean column rendered the raw key `grid.booleanTrue`. That key exists in no bundle, and the English fallback was passed as a bare second argument, which the no-provider translator reads as an options object. Switched to the `grid.yes`/`grid.no` keys the cell renderer and BulkActionDialog already use, with `defaultValue`. Covered by a regression test confirmed to fail against the old code. The rest are type-only and preserve runtime behaviour: `scorePair`'s closure-mutated `let`s folded into one record so the TS2367 type gate narrows correctly (gate unchanged), `SectionFieldsContext.fieldLabel` aligned with its `@object-ui/i18n` producer (cleared six errors), `MasterDetailFormSchema.recordId` widened to `string | number` with `String()` only at the batch-transaction boundary, an explicit `fillPriority` for the optional `GridColumn.type`, an unused parameter prefixed `_`, and a stale `@ts-expect-error` dropped. vscode-extension migrates off the deprecated node10 `moduleResolution: "node"` to `node16`; its count was under-reported as 1 because that TS5107 config error masked four missing Node globals — `@types/node` added with an explicit `types`. plugin-grid/plugin-form/plugin-designer gain the `baseUrl` + `paths` override their type-checked peers carry (cli an empty `paths`), without which the inherited root `paths` produce the spurious TS6059 noise described in #2915. Verified by injecting a type error into each of the five: `pnpm type-check` now fails for every one, which was impossible before. Refs #2911, #2915 Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent f1abf0e commit dc7a798

22 files changed

Lines changed: 255 additions & 36 deletions

.changeset/typecheck-debt-2919.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
---
2+
"@object-ui/plugin-grid": patch
3+
"@object-ui/plugin-form": patch
4+
"@object-ui/plugin-designer": patch
5+
"@object-ui/cli": patch
6+
"object-ui": patch
7+
---
8+
9+
fix(plugin-grid,plugin-form,plugin-designer,cli,vscode-extension): type-check the last five unchecked packages, and fix the two runtime bugs that hid there (#2919)
10+
11+
Closes the remaining `DEBT` entries from the #2911 sweep. Each package gains
12+
`"type-check": "tsc --noEmit"` and loses its entry in
13+
`scripts/check-type-check-coverage.mjs`; coverage goes 36 -> 41 of 45 and
14+
outstanding errors 25 -> 5 (only #2916 `plugin-view` and #2918 `layout` remain).
15+
16+
**Two of these were real bugs, not just type noise.**
17+
18+
`@object-ui/cli``objectui validate` could never report a validation failure.
19+
`ZodError.errors` was removed in Zod 4 (the repo is on 4.4.3), so `.errors` read
20+
`undefined` and `.forEach` threw a `TypeError` that the enclosing `catch`
21+
reported as `✗ Error reading or parsing schema file: Cannot read properties of
22+
undefined` — swallowing the very errors the command exists to print. Now reads
23+
`.issues`. Verified against the built CLI: an invalid schema now prints
24+
`1. Invalid input / Code: invalid_union` and exits 1.
25+
26+
`@object-ui/plugin-grid` — grouping a grid by a boolean column showed the raw
27+
i18n key. `t('grid.booleanTrue', 'Yes')` asked for a key present in neither
28+
`GRID_DEFAULT_TRANSLATIONS` nor any locale bundle, and passed the English
29+
fallback as a bare second argument — which `createSafeTranslation`'s no-provider
30+
translator reads as an *options object*, so the fallback never applied and the
31+
header rendered the literal `grid.booleanTrue`. Switched to the `grid.yes` /
32+
`grid.no` keys the boolean cell renderer (`ObjectGrid.tsx`) and
33+
`BulkActionDialog` already use, with the fallback passed as `defaultValue`.
34+
Covered by a new regression test, confirmed to fail against the old code.
35+
36+
The rest are type-only corrections that preserve runtime behaviour exactly:
37+
38+
- **plugin-grid** `importParsers.ts``scorePair`'s `score`/`reason` moved into
39+
one `best` record. They were captured `let`s mutated only inside the `bump`
40+
closure, which TypeScript's control-flow analysis does not track, so it still
41+
believed `reason` was `'none'` at the type gate and flagged the comparisons as
42+
non-overlapping (TS2367). The gate — which stops a text column being mapped
43+
onto a number field — is unchanged; its two dedicated tests still pass.
44+
- **plugin-form**`SectionFieldsContext.fieldLabel` now requires `fallback`,
45+
matching the `useSafeFieldLabel` producer in `@object-ui/i18n` (an omitted
46+
fallback could not satisfy the `=> string` return, and all four call sites
47+
already pass one). This one signature cleared six errors.
48+
`MasterDetailFormSchema.recordId` widens to `string | number`, matching
49+
`ObjectFormSchema` and the five envelopes that forward straight into it;
50+
it is narrowed with `String()` only at the batch-transaction boundary, whose
51+
`BatchTransactionOperation.id` is a string by protocol (the `isEdit` guard
52+
already proves it non-null there). `deriveMasterDetail`'s column sort gets an
53+
explicit `fillPriority` helper — `GridColumn.type` is optional, and a column
54+
without one keeps sorting at priority 5 exactly as the old
55+
`TYPE_FILL_PRIORITY[undefined] ?? 5` lookup put it.
56+
- **plugin-designer** — unused `index` parameter prefixed `_`, matching the
57+
`_entry` beside it.
58+
- **cli** — a stale `@ts-expect-error` removed; `viteConfig` is typed `any`, so
59+
the line it guarded had stopped erroring.
60+
- **vscode-extension** (`object-ui`) — migrated off `moduleResolution: "node"`,
61+
which is deprecated and stops working in TypeScript 7, to `node16` paired with
62+
`module: "node16"` (the package has no `"type": "module"`, so node16 resolves
63+
it as the CommonJS that tsup emits, and it gains the `exports`-map awareness
64+
node10 lacks). Its error count was under-reported as 1: that TS5107 config
65+
error masked four more. The package uses `console`/`Buffer` but sets
66+
`lib: ["ES2020"]` with no DOM and never declared `@types/node` — added, with an
67+
explicit `types: ["node", "vscode"]`.
68+
69+
Also: `plugin-grid`, `plugin-form` and `plugin-designer` gain the `baseUrl` +
70+
`paths` override their type-checked plugin peers already carry, and `cli` an
71+
empty `paths`. Without it the inherited root `paths` point `@object-ui/*` at
72+
sibling `src/`, which is outside each project's `rootDir` and produces the ~104
73+
spurious TS6059 errors noted in #2915; workspace deps instead resolve through
74+
node_modules to built `.d.ts`, which `type-check`'s `dependsOn: ["^build"]`
75+
guarantees exist.
76+
77+
Verified the gate genuinely covers all five rather than trusting the green:
78+
injecting a type error into each package makes `pnpm type-check --filter <pkg>`
79+
fail, which was impossible before this change.

packages/cli/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
"build": "tsup",
3434
"dev": "tsup --watch",
3535
"lint": "eslint src",
36-
"test": "vitest run"
36+
"test": "vitest run",
37+
"type-check": "tsc --noEmit"
3738
},
3839
"keywords": [
3940
"objectstack",

packages/cli/src/commands/dev.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,6 @@ export async function dev(schemaPath: string, options: DevOptions) {
197197
// We might get the cjs entry, but for aliasing usually fine.
198198
// Better yet, if we can find the package root, but require.resolve gives file.
199199
// Let's just use what require.resolve gives.
200-
// @ts-expect-error - lucidePath is dynamically resolved
201200
viteConfig.resolve.alias['lucide-react'] = lucidePath;
202201
} catch (e) {
203202
console.warn('⚠️ Could not resolve lucide-react automatically:', e);

packages/cli/src/commands/validate.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,18 @@ export async function validate(schemaPath: string) {
8383
console.error(chalk.red('✗ Schema validation failed!\n'));
8484
console.error(chalk.bold('Validation Errors:'));
8585

86-
// Format Zod errors nicely
87-
const errors = result.error.errors;
88-
errors.forEach((error, index) => {
89-
console.error(chalk.red(`\n${index + 1}. ${error.message}`));
90-
if (error.path && error.path.length > 0) {
91-
console.error(chalk.gray(` Path: ${error.path.join(' → ')}`));
86+
// Format Zod errors nicely. `.issues` is the only accessor on a Zod 4
87+
// ZodError — the `.errors` alias was removed, so reading it yielded
88+
// undefined and this loop threw a TypeError that the catch below
89+
// reported as "Error reading or parsing schema file", hiding the very
90+
// errors this command exists to print.
91+
result.error.issues.forEach((issue, index) => {
92+
console.error(chalk.red(`\n${index + 1}. ${issue.message}`));
93+
if (issue.path && issue.path.length > 0) {
94+
console.error(chalk.gray(` Path: ${issue.path.join(' → ')}`));
9295
}
93-
if (error.code) {
94-
console.error(chalk.gray(` Code: ${error.code}`));
96+
if (issue.code) {
97+
console.error(chalk.gray(` Code: ${issue.code}`));
9598
}
9699
});
97100

packages/cli/tsconfig.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@
77
"target": "ES2020",
88
"lib": ["ES2020"],
99
"moduleResolution": "bundler",
10-
"resolveJsonModule": true
10+
"resolveJsonModule": true,
11+
"baseUrl": ".",
12+
// Drop the repo-root `paths` (which point `@object-ui/*` at sibling
13+
// `src/`): with `rootDir: ./src` those sources are outside this project
14+
// and every one of them reports TS6059. Workspace deps resolve through
15+
// node_modules to their built types, which `type-check` depends on.
16+
"paths": {}
1117
},
1218
"include": ["src/**/*"],
1319
"exclude": ["node_modules", "dist"]

packages/plugin-designer/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"build": "vite build",
2525
"clean": "rm -rf dist",
2626
"test": "vitest run",
27+
"type-check": "tsc --noEmit",
2728
"lint": "eslint ."
2829
},
2930
"peerDependencies": {

packages/plugin-designer/src/components/HistoryPanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export function HistoryPanel<T>({
5454
const { timeline, currentIndex, undo, redo, jumpTo, canUndo, canRedo } = history;
5555

5656
const defaultRenderLabel = React.useCallback(
57-
(_entry: T, index: number, position: number) => {
57+
(_entry: T, _index: number, position: number) => {
5858
if (position === 0) return 'Current';
5959
if (position < 0) return `Earlier (${Math.abs(position)} step${Math.abs(position) > 1 ? 's' : ''} back)`;
6060
return `Later (${position} step${position > 1 ? 's' : ''} forward)`;

packages/plugin-designer/tsconfig.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
"compilerOptions": {
44
"outDir": "dist",
55
"jsx": "react-jsx",
6+
"baseUrl": ".",
7+
"paths": {
8+
"@/*": ["src/*"]
9+
},
10+
"noEmit": false,
11+
"declaration": true,
12+
"composite": true,
13+
"declarationMap": true,
614
"skipLibCheck": true
715
},
816
"include": ["src"],

packages/plugin-form/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"scripts": {
1818
"build": "vite build",
1919
"test": "vitest run",
20+
"type-check": "tsc --noEmit",
2021
"lint": "eslint ."
2122
},
2223
"dependencies": {

packages/plugin-form/src/MasterDetailForm.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,14 @@ export interface MasterDetailFormSchema {
7575
/** Parent object name, e.g. 'expense_claim'. */
7676
objectName: string;
7777
mode?: 'create' | 'edit';
78-
recordId?: string;
78+
/**
79+
* `string | number` to match `ObjectFormSchema` and the drawer/modal/split/
80+
* tabbed/wizard envelopes that hand a record straight through to this form —
81+
* a numeric primary key is a real backend shape. Narrowed to a string only at
82+
* the batch-transaction boundary, whose `BatchTransactionOperation.id` is a
83+
* string by protocol.
84+
*/
85+
recordId?: string | number;
7986
/** Prefilled parent header values (create mode) — seeds the parent form's
8087
* initial values, e.g. a conversion wizard carrying the lead/account over. */
8188
initialValues?: Record<string, any>;
@@ -554,7 +561,7 @@ export const MasterDetailForm: React.FC<MasterDetailFormProps> = ({
554561
const ops = isEdit
555562
? buildMasterDetailEditBatch(
556563
schema.objectName,
557-
schema.recordId!,
564+
String(schema.recordId),
558565
parentData,
559566
details.filter((d) => d.relationshipField).map((d, i) => ({
560567
childObject: d.childObject,

0 commit comments

Comments
 (0)