Skip to content

Commit c791677

Browse files
serpentbladeclaude
andcommitted
fix(63-12): WR-02 — emit numeric-attr bindings raw when nullability is unprovable (TS2869 guard)
WR-02/IN-01 (review): isProvablyNonNullishNumeric only recognized numeric literals, arithmetic, and unary +/-, so a numeric-attr binding whose expression is a bald number-returning CallExpression/MemberExpression or a ||-fallback fell through to '{name={(expr) ?? undefined}}'. When the left operand is statically 'number' (never nullish) that right operand is unreachable -> TS2869. Real in the shipped leaf: aria-rowcount={(totalRowCount()) ?? undefined} only survived by 'any'-inference. Broaden the recognizer to also treat CallExpression/MemberExpression and a LogicalExpression (||/&&/?? with a non-nullish RHS) as non-nullish, so the numeric binding emits RAW (React's 'number | undefined' slot accepts a bare 'number'; a runtime null/undefined is still dropped by React). The genuinely- nullish ConditionalExpression form (x ? n : null -> normalized : undefined) is NOT recognized, so it still gets the reachable '?? undefined' drop. IN-01: the non-virtual aria-rowcount={totalRowCount()} now emits raw, matching the virtual aria-rowcount={rows.length} path. Solid needs no symmetric change: it keeps numeric ARIA on the rozieAttr path (loosely typed), and its BOOLEAN_NULLISH_ARIA_ATTRS path is already hasNullishBranch-gated (no TS2869 gap). Re-emitted x6. Cross-family churn is the expected clean raw-flip on numeric tabIndex call bindings (date-picker/pagination/switch) — runtime-identical. Gate: build x6, typecheck 278/278, dist-parity 1001/1001 (no drift, AttrNullishDrop intact), target suites 112/113 (sole failure = pre-existing command-palette/listbox surface-hash drift, unrelated). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KSYH6VBAJwa7nYy4AksuNH
1 parent 8867a6f commit c791677

5 files changed

Lines changed: 50 additions & 25 deletions

File tree

packages/targets/react/src/emit/emitTemplateAttribute.ts

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -208,17 +208,42 @@ function hasNullishBranch(expr: t.Expression): boolean {
208208
}
209209

210210
/**
211-
* A numeric-attribute binding that is SYNTACTICALLY provably non-nullish — a numeric literal,
212-
* an arithmetic BinaryExpression (`a + 1`, `i * 2`), or a unary `+`/`-`. These already type as
213-
* `number`, so the numeric-attr emit must NOT append `?? undefined` (the right operand would be
214-
* unreachable → TS2869). Nullish-capable forms (a `x ? n : null` ternary normalized to
215-
* `undefined`, a `number | null`-returning call) fall through to the `?? undefined` drop.
211+
* A numeric-attribute binding the emitter should emit RAW (`name={expr}`) rather than wrapping in
212+
* the `?? undefined` nullish-drop. The `?? undefined` rescue exists ONLY to keep a PROVABLY-nullish
213+
* numeric binding (a `x ? n : null` ternary, normalized to `x ? n : undefined`) typed
214+
* `number | undefined` while dropping the attribute on the nullish branch. For every OTHER shape we
215+
* prefer RAW: appending `?? undefined` to an operand that is statically `number` (never nullish)
216+
* makes the right operand unreachable → TS2869 (WR-02 — the shipped
217+
* `aria-rowcount={(totalRowCount()) ?? undefined}` only dodged it by `any`-inference; a strictly-
218+
* typed `number`-returning helper, or a strict re-typecheck of the leaf, would break).
219+
*
220+
* Recognized as non-nullish (→ raw), mirroring the `.length` member that already emits raw via
221+
* `wrapForDisplay=false`:
222+
* - a numeric literal, an arithmetic BinaryExpression (`a + 1`, `i * 2`), a unary `+`/`-`;
223+
* - a CallExpression / MemberExpression (`totalRowCount()`, `rows.length`) — the IR cannot prove
224+
* these nullable from the AST; React's `number | undefined` slot accepts a bare `number`, and
225+
* a runtime `null`/`undefined` is still dropped by React (IN-01 — the non-virtual
226+
* `aria-rowcount={totalRowCount()}` now matches the virtual `aria-rowcount={rows.length}`);
227+
* - a LogicalExpression `a || n` / `a ?? n` / `a && n` whose RHS is itself non-nullish
228+
* (`span || 1`) — the `||`/`??` fallback (and a non-nullish `&&` RHS) yields a non-nullish result.
229+
* The genuinely-nullish ConditionalExpression form (`x ? n : null`) is deliberately NOT recognized
230+
* here, so it still falls through to the `?? undefined` drop (its `: undefined` branch makes the
231+
* right operand reachable → no TS2869).
216232
*/
217233
const ARITHMETIC_BINARY_OPS: ReadonlySet<string> = new Set(['+', '-', '*', '/', '%', '**']);
234+
const NON_NULLISH_LOGICAL_OPS: ReadonlySet<string> = new Set(['||', '&&', '??']);
218235
function isProvablyNonNullishNumeric(expr: t.Expression): boolean {
219236
if (t.isNumericLiteral(expr)) return true;
220237
if (t.isBinaryExpression(expr)) return ARITHMETIC_BINARY_OPS.has(expr.operator);
221238
if (t.isUnaryExpression(expr)) return expr.operator === '+' || expr.operator === '-';
239+
// A bald call / member access — nullability is unprovable from the AST; prefer RAW over an
240+
// unconditional `?? undefined` that is TS2869 when the value is statically `number`.
241+
if (t.isCallExpression(expr) || t.isOptionalCallExpression(expr)) return true;
242+
if (t.isMemberExpression(expr) || t.isOptionalMemberExpression(expr)) return true;
243+
// `a || 1` / `a ?? 1` / `a && 1` — a non-nullish RHS fallback yields a non-nullish result.
244+
if (t.isLogicalExpression(expr) && NON_NULLISH_LOGICAL_OPS.has(expr.operator)) {
245+
return isProvablyNonNullishNumeric(expr.right);
246+
}
222247
return false;
223248
}
224249

@@ -1064,12 +1089,12 @@ function emitNonClassAttribute(
10641089
// JSX omits the attribute), matching the `rozieAttr` drop semantics for
10651090
// the numeric case without the string widening.
10661091
if (NUMERIC_HTML_ATTRS.has(attr.name.toLowerCase())) {
1067-
// A provably non-nullish numeric expression (`wr.vi.index + 1`, a numeric literal,
1068-
// a unary `+`/`-`) is already `number` — appending `?? undefined` makes the right
1069-
// operand unreachable (TS2869). Emit it RAW (still `number`, typechecks against the
1070-
// `number | undefined` ARIA/HTML slot). Only nullish-capable forms (a `x ? n : null`
1071-
// ternary normalized to `undefined`, a `number | null`-returning call like
1072-
// `cellTabindex()`) need the `?? undefined` nullish-drop.
1092+
// WR-02: emit RAW whenever nullability is NOT provable from the AST — a numeric literal,
1093+
// arithmetic, unary `+`/`-`, a bald call/member (`totalRowCount()`, `rows.length`), or a
1094+
// `|| n`/`?? n` fallback. These are `number`(-ish); appending `?? undefined` to a static
1095+
// `number` makes the right operand unreachable (TS2869). Only the genuinely-nullish
1096+
// ConditionalExpression form (`x ? n : null` → normalized `: undefined`) falls through to
1097+
// the `?? undefined` drop below (reachable → no TS2869), preserving the attribute-drop.
10731098
if (isProvablyNonNullishNumeric(normalizedAttrExpr)) {
10741099
return { jsx: `${jsxName}={${exprCode}}`, diagnostics };
10751100
}

packages/ui/data-table/packages/react/src/DataTable.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3833,7 +3833,7 @@ const DataTable = forwardRef<DataTableHandle, DataTableProps>(function DataTable
38333833
<table className={clsx("rozie-data-table", { "rdt-sticky": props.stickyHeader })} role={rozieAttr(tableRole())} aria-rowcount={rows.length} onKeyDown={($event) => { onGridKeyDown($event); }} onFocus={($event) => { syncActiveFromEvent($event); }} onBlur={($event) => { onGridFocusOut($event); }} onMouseDown={($event) => { onGridMouseDown($event); }} data-rozie-s-d5dcab4c="">
38343834
<thead className={"rdt-thead"} role="rowgroup" data-rozie-s-d5dcab4c="">
38353835
{headerGroups.map((hg, hgLevel) => <tr key={hg.id} className={"rdt-tr"} role="row" data-rozie-s-d5dcab4c="">
3836-
{hg.headers.map((header) => <th key={header.id} className={clsx("rdt-th", { "rdt-select-th": isSelectColumn(header.column.id), "rdt-th-resizing": columnIsResizing(header.column.id) })} role="columnheader" data-col={rozieAttr(header.column.id)} data-grid-cell="" data-row="__header" data-header-level={rozieAttr(hgLevel)} colSpan={(header.colSpan > 1 ? header.colSpan : undefined) ?? undefined} data-col-index={rozieAttr(headerColIndexOf(hg, header))} tabIndex={(cellTabindex('__header', headerColIndexOf(hg, header), hgLevel)) ?? undefined} aria-sort={rozieAttr(ariaSortFor(header.column.id))} style={parseInlineStyle(thStyle(header.column.id))} data-rozie-s-d5dcab4c="">
3836+
{hg.headers.map((header) => <th key={header.id} className={clsx("rdt-th", { "rdt-select-th": isSelectColumn(header.column.id), "rdt-th-resizing": columnIsResizing(header.column.id) })} role="columnheader" data-col={rozieAttr(header.column.id)} data-grid-cell="" data-row="__header" data-header-level={rozieAttr(hgLevel)} colSpan={(header.colSpan > 1 ? header.colSpan : undefined) ?? undefined} data-col-index={rozieAttr(headerColIndexOf(hg, header))} tabIndex={cellTabindex('__header', headerColIndexOf(hg, header), hgLevel)} aria-sort={rozieAttr(ariaSortFor(header.column.id))} style={parseInlineStyle(thStyle(header.column.id))} data-rozie-s-d5dcab4c="">
38373837
{(isSelectColumn(header.column.id)) ? <span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
38383838
{(props.renderSelectAll ?? props.slots?.['selectAll']) ? ((props.renderSelectAll ?? props.slots?.['selectAll']) as Function)({ checked: isAllRowsSelected(), indeterminate: isSomeRowsSelected(), toggle: onToggleAllRows }) : (props.selectionMode === 'multiple') && <input className={"rdt-select-all"} type="checkbox" aria-label="Select all rows" checked={isAllRowsSelected()} onChange={($event) => { onToggleAllRows($event); }} data-rozie-s-d5dcab4c="" />}
38393839
</span> : <span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
@@ -3861,12 +3861,12 @@ const DataTable = forwardRef<DataTableHandle, DataTableProps>(function DataTable
38613861
<tbody className={"rdt-tbody"} role="rowgroup" data-rozie-s-d5dcab4c="">
38623862

38633863
<tr className={"rdt-spacer"} aria-hidden="true" data-rozie-s-d5dcab4c="">
3864-
<td colSpan={(visibleColCount()) ?? undefined} style={parseInlineStyle('height:' + padTop() + 'px;padding:0;border:0')} data-rozie-s-d5dcab4c="" />
3864+
<td colSpan={visibleColCount()} style={parseInlineStyle('height:' + padTop() + 'px;padding:0;border:0')} data-rozie-s-d5dcab4c="" />
38653865
</tr>
38663866

38673867
{windowedRows().map((wr) => <Fragment key={wr.row.id}>
38683868
<tr key={wr.row.id} className={clsx("rdt-tr", { "rdt-group-header": rowIsGrouped(wr.row), "rdt-row-pinned": wr.pinned })} role="row" data-row={rozieAttr(wr.vi.index)} aria-rowindex={wr.vi.index + 1} data-index={rozieAttr(wr.vi.index)} data-pinned={rozieAttr(wr.pinned ? 'true' : undefined)} data-depth={rozieAttr(wr.row.depth)} data-group-header={rozieAttr(rowIsGrouped(wr.row) ? wr.row.id : undefined)} data-group-leaf={rozieAttr(groupingActive() && !rowIsGrouped(wr.row) ? wr.row.id : undefined)} aria-expanded={(rowIsGrouped(wr.row) ? !!rowIsExpanded(wr.row) : undefined) ?? undefined} aria-level={(groupingActive() ? wr.row.depth + 1 : undefined) ?? undefined} data-rozie-s-d5dcab4c="">
3869-
{visibleCellsFor(wr.row).map((cellCtx) => <td key={cellCtx.id} className={clsx("rdt-td", { "rdt-select-td": isSelectColumn(cellCtx.column.id), "rdt-in-range": inRange(wr.vi.index, colIndexOf(wr.row, cellCtx)) })} role={rozieAttr(cellRole())} data-col={rozieAttr(cellCtx.column.id)} data-grid-cell="" data-row={rozieAttr(wr.vi.index)} data-col-index={rozieAttr(colIndexOf(wr.row, cellCtx))} tabIndex={(cellTabindex(String(wr.vi.index), colIndexOf(wr.row, cellCtx))) ?? undefined} style={parseInlineStyle(bodyCellStyle(wr.row, cellCtx.column.id))} aria-invalid={rozieAttr(cellAriaInvalid(wr.vi.index, colIndexOf(wr.row, cellCtx)))} data-in-range={rozieAttr(inRange(wr.vi.index, colIndexOf(wr.row, cellCtx)) ? 'true' : undefined)} data-agg-cell={rozieAttr(cellIsAggregated(cellCtx) ? cellCtx.column.id : undefined)} data-rozie-s-d5dcab4c="">
3869+
{visibleCellsFor(wr.row).map((cellCtx) => <td key={cellCtx.id} className={clsx("rdt-td", { "rdt-select-td": isSelectColumn(cellCtx.column.id), "rdt-in-range": inRange(wr.vi.index, colIndexOf(wr.row, cellCtx)) })} role={rozieAttr(cellRole())} data-col={rozieAttr(cellCtx.column.id)} data-grid-cell="" data-row={rozieAttr(wr.vi.index)} data-col-index={rozieAttr(colIndexOf(wr.row, cellCtx))} tabIndex={cellTabindex(String(wr.vi.index), colIndexOf(wr.row, cellCtx))} style={parseInlineStyle(bodyCellStyle(wr.row, cellCtx.column.id))} aria-invalid={rozieAttr(cellAriaInvalid(wr.vi.index, colIndexOf(wr.row, cellCtx)))} data-in-range={rozieAttr(inRange(wr.vi.index, colIndexOf(wr.row, cellCtx)) ? 'true' : undefined)} data-agg-cell={rozieAttr(cellIsAggregated(cellCtx) ? cellCtx.column.id : undefined)} data-rozie-s-d5dcab4c="">
38703870

38713871
{(isExpanderColumn(cellCtx.column.id)) ? <span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
38723872
{(rowCanExpand(wr.row)) && <button type="button" className={"rdt-expander"} data-expander="" aria-expanded={!!rowIsExpanded(wr.row)} aria-label={rozieAttr(rowIsExpanded(wr.row) ? 'Collapse row' : 'Expand row')} onClick={($event) => { onToggleExpand(wr.row, $event); }} data-rozie-s-d5dcab4c="">{rozieDisplay(rowIsExpanded(wr.row) ? '▾' : '▸')}</button>}</span> : (isSelectColumn(cellCtx.column.id)) ? <span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
@@ -3888,20 +3888,20 @@ const DataTable = forwardRef<DataTableHandle, DataTableProps>(function DataTable
38883888
</tr>
38893889

38903890
{(rowShowsDetail(wr.row)) && <tr key={wr.row.id} className={"rdt-detail-row"} role="row" data-detail-row={rozieAttr(wr.row.id)} data-rozie-s-d5dcab4c="">
3891-
<td className={"rdt-detail-cell"} colSpan={(visibleColCount()) ?? undefined} data-rozie-s-d5dcab4c="">
3891+
<td className={"rdt-detail-cell"} colSpan={visibleColCount()} data-rozie-s-d5dcab4c="">
38923892
{(props.renderDetail ?? props.slots?.['detail'])?.({ row: wr.row.original })}
38933893
</td>
38943894
</tr>}</Fragment>)}
38953895

38963896
<tr className={"rdt-spacer"} aria-hidden="true" data-rozie-s-d5dcab4c="">
3897-
<td colSpan={(visibleColCount()) ?? undefined} style={parseInlineStyle('height:' + padBottom() + 'px;padding:0;border:0')} data-rozie-s-d5dcab4c="" />
3897+
<td colSpan={visibleColCount()} style={parseInlineStyle('height:' + padBottom() + 'px;padding:0;border:0')} data-rozie-s-d5dcab4c="" />
38983898
</tr>
38993899
</tbody>
39003900
</table>
3901-
</div> : <table className={clsx("rozie-data-table", { "rdt-sticky": props.stickyHeader })} role={rozieAttr(tableRole())} aria-rowcount={(totalRowCount()) ?? undefined} onKeyDown={($event) => { onGridKeyDown($event); }} onFocus={($event) => { syncActiveFromEvent($event); }} onBlur={($event) => { onGridFocusOut($event); }} onMouseDown={($event) => { onGridMouseDown($event); }} data-rozie-s-d5dcab4c="">
3901+
</div> : <table className={clsx("rozie-data-table", { "rdt-sticky": props.stickyHeader })} role={rozieAttr(tableRole())} aria-rowcount={totalRowCount()} onKeyDown={($event) => { onGridKeyDown($event); }} onFocus={($event) => { syncActiveFromEvent($event); }} onBlur={($event) => { onGridFocusOut($event); }} onMouseDown={($event) => { onGridMouseDown($event); }} data-rozie-s-d5dcab4c="">
39023902
<thead className={"rdt-thead"} role="rowgroup" data-rozie-s-d5dcab4c="">
39033903
{headerGroups.map((hg, hgLevel) => <tr key={hg.id} className={"rdt-tr"} role="row" data-rozie-s-d5dcab4c="">
3904-
{hg.headers.map((header) => <th key={header.id} className={clsx("rdt-th", { "rdt-select-th": isSelectColumn(header.column.id), "rdt-th-resizing": columnIsResizing(header.column.id) })} role="columnheader" data-col={rozieAttr(header.column.id)} data-grid-cell="" data-row="__header" data-header-level={rozieAttr(hgLevel)} colSpan={(header.colSpan > 1 ? header.colSpan : undefined) ?? undefined} data-col-index={rozieAttr(headerColIndexOf(hg, header))} tabIndex={(cellTabindex('__header', headerColIndexOf(hg, header), hgLevel)) ?? undefined} aria-sort={rozieAttr(ariaSortFor(header.column.id))} style={parseInlineStyle(thStyle(header.column.id))} data-rozie-s-d5dcab4c="">
3904+
{hg.headers.map((header) => <th key={header.id} className={clsx("rdt-th", { "rdt-select-th": isSelectColumn(header.column.id), "rdt-th-resizing": columnIsResizing(header.column.id) })} role="columnheader" data-col={rozieAttr(header.column.id)} data-grid-cell="" data-row="__header" data-header-level={rozieAttr(hgLevel)} colSpan={(header.colSpan > 1 ? header.colSpan : undefined) ?? undefined} data-col-index={rozieAttr(headerColIndexOf(hg, header))} tabIndex={cellTabindex('__header', headerColIndexOf(hg, header), hgLevel)} aria-sort={rozieAttr(ariaSortFor(header.column.id))} style={parseInlineStyle(thStyle(header.column.id))} data-rozie-s-d5dcab4c="">
39053905

39063906

39073907
{(isSelectColumn(header.column.id)) ? <span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
@@ -3935,7 +3935,7 @@ const DataTable = forwardRef<DataTableHandle, DataTableProps>(function DataTable
39353935

39363936
{rows.map((row) => <Fragment key={row.id}>
39373937
<tr key={row.id} className={clsx("rdt-tr", { "rdt-group-header": rowIsGrouped(row) })} role="row" data-depth={rozieAttr(row.depth)} aria-rowindex={(isGrid() ? absRowIndexOf(row) + 1 : undefined) ?? undefined} data-group-header={rozieAttr(rowIsGrouped(row) ? row.id : undefined)} data-group-leaf={rozieAttr(groupingActive() && !rowIsGrouped(row) ? row.id : undefined)} aria-expanded={(rowIsGrouped(row) ? !!rowIsExpanded(row) : undefined) ?? undefined} aria-level={(groupingActive() ? row.depth + 1 : undefined) ?? undefined} data-rozie-s-d5dcab4c="">
3938-
{visibleCellsFor(row).map((cellCtx) => <td key={cellCtx.id} className={clsx("rdt-td", { "rdt-select-td": isSelectColumn(cellCtx.column.id), "rdt-in-range": inRange(rowIndexOf(row), colIndexOf(row, cellCtx)) })} role={rozieAttr(cellRole())} data-col={rozieAttr(cellCtx.column.id)} data-grid-cell="" data-row={rozieAttr(rowIndexOf(row))} data-col-index={rozieAttr(colIndexOf(row, cellCtx))} tabIndex={(cellTabindex(String(rowIndexOf(row)), colIndexOf(row, cellCtx))) ?? undefined} style={parseInlineStyle(bodyCellStyle(row, cellCtx.column.id))} aria-invalid={rozieAttr(cellAriaInvalid(rowIndexOf(row), colIndexOf(row, cellCtx)))} data-in-range={rozieAttr(inRange(rowIndexOf(row), colIndexOf(row, cellCtx)) ? 'true' : undefined)} data-agg-cell={rozieAttr(cellIsAggregated(cellCtx) ? cellCtx.column.id : undefined)} data-rozie-s-d5dcab4c="">
3938+
{visibleCellsFor(row).map((cellCtx) => <td key={cellCtx.id} className={clsx("rdt-td", { "rdt-select-td": isSelectColumn(cellCtx.column.id), "rdt-in-range": inRange(rowIndexOf(row), colIndexOf(row, cellCtx)) })} role={rozieAttr(cellRole())} data-col={rozieAttr(cellCtx.column.id)} data-grid-cell="" data-row={rozieAttr(rowIndexOf(row))} data-col-index={rozieAttr(colIndexOf(row, cellCtx))} tabIndex={cellTabindex(String(rowIndexOf(row)), colIndexOf(row, cellCtx))} style={parseInlineStyle(bodyCellStyle(row, cellCtx.column.id))} aria-invalid={rozieAttr(cellAriaInvalid(rowIndexOf(row), colIndexOf(row, cellCtx)))} data-in-range={rozieAttr(inRange(rowIndexOf(row), colIndexOf(row, cellCtx)) ? 'true' : undefined)} data-agg-cell={rozieAttr(cellIsAggregated(cellCtx) ? cellCtx.column.id : undefined)} data-rozie-s-d5dcab4c="">
39393939

39403940
{(isExpanderColumn(cellCtx.column.id)) ? <span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
39413941
{(rowCanExpand(row)) && <button type="button" className={"rdt-expander"} data-expander="" aria-expanded={!!rowIsExpanded(row)} aria-label={rozieAttr(rowIsExpanded(row) ? 'Collapse row' : 'Expand row')} onClick={($event) => { onToggleExpand(row, $event); }} data-rozie-s-d5dcab4c="">{rozieDisplay(rowIsExpanded(row) ? '▾' : '▸')}</button>}</span> : (isSelectColumn(cellCtx.column.id)) ? <span style={{ display: "contents" }} data-rozie-s-d5dcab4c="">
@@ -3957,7 +3957,7 @@ const DataTable = forwardRef<DataTableHandle, DataTableProps>(function DataTable
39573957
</tr>
39583958

39593959
{(rowShowsDetail(row)) && <tr key={row.id} className={"rdt-detail-row"} role="row" data-detail-row={rozieAttr(row.id)} data-rozie-s-d5dcab4c="">
3960-
<td className={"rdt-detail-cell"} colSpan={(visibleColCount()) ?? undefined} data-rozie-s-d5dcab4c="">
3960+
<td className={"rdt-detail-cell"} colSpan={visibleColCount()} data-rozie-s-d5dcab4c="">
39613961
{(props.renderDetail ?? props.slots?.['detail'])?.({ row: row.original })}
39623962
</td>
39633963
</tr>}</Fragment>)}

0 commit comments

Comments
 (0)