Skip to content

fix(types): make the 8-literal union the one canonical TableColumn.type - #6370

Merged
os-support-ai merged 1 commit into
mainfrom
claude/issue-5853-tablecolumn-type-canonical-union
Aug 25, 2026
Merged

fix(types): make the 8-literal union the one canonical TableColumn.type#6370
os-support-ai merged 1 commit into
mainfrom
claude/issue-5853-tablecolumn-type-canonical-union

Conversation

@claude

@claude claude Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Fixes #5853

TableColumn.type had three disagreeing authorities. The interface declared 8 literals, the
zod mirror declared z.string() and accepted anything, and the renderer branched on a third
set it could only reach through an as any cast.

Implements the operative maintainer ruling — 2026-08-25, os-steve, verbatim 「其他同意」,
Option B: the 8-literal interface union is the canonical value set. The earlier
2026-08-24 ruling (canonical = the renderer's live read set, 12 values) is superseded and is
not what this PR builds.


1. Corpus measurement — ran FIRST, before any edit (the fork clause)

The ruling's step 1: measure authored out-of-union spellings before tightening, and stop
if authored usage is material
. It is not material. It is zero.

Method, with a control probe so a zero-hit reads as real. A structured walk of every
JSON file under examples/, content/, apps/, e2e/, docs/ and packages/
(591 files parsed), collecting every object carrying accessorKey (the TableColumn
discriminator) and every object inside any columns array, plus a regex sweep of the
non-JSON corpus. Control: type: 'currency' → 22 hits; type: 'banana' → 0 hits.

measurement result
authored type: 'int' in a column position — repo-wide 0
authored type: 'integer' in a column position — repo-wide 0
authored type: 'float' in a column position — repo-wide 0
authored type: 'double' in a column position — repo-wide 0
objects with accessorKey carrying a type 3 (number, text, currency — all declared)
objects with accessorKey carrying an OUT-OF-UNION type 0

The only out-of-union hits in the whole corpus, with the file list the ruling asked for:

  • examples/schema-catalog/src/schemas/plugin-grid/object-grid-columns.json$.columns[2], $.columns[4]type: "select"
  • examples/schema-catalog/src/schemas/plugin-grid/object-grid-selection-summaries.json$.columns[3], $.columns[4]type: "select"
  • content/docs/plugins/plugin-grid.mdxtype: "select" / type: "badge" in object-grid examples

None of these is a TableColumn. Their keys are field + type, which is
ListColumn (@objectstack/spec/ui) — ObjectGrid's column INPUT vocabulary, a different
declared type that this PR does not touch. They are authored against the spec's field-type
vocabulary and keep validating exactly as before. They matter here only as evidence for §4:
they are what flows INTO the inference whose output §3 folds.

The three repo-wide type: 'integer' hits are JSON-Schema primitives in
packages/app-shell/.../json-schema-to-fields, unrelated to this key.

Verdict: fork clause not triggered — no authored document breaks.

2. Coordinates re-derived on this branch's base (e4559d1e0)

Both rulings' line numbers were stale, and so were the PM's.

item ruling / card / dispatch said measured on e4559d1e0
the as any cast :2151 (ruling) / :2152 (card) / :2192 (dispatch) packages/components/src/renderers/complex/data-table.tsx:2192 ✅ dispatch correct
ObjectGrid emit seam :2206 (ruling) / :2236 (dispatch) packages/plugin-grid/src/ObjectGrid.tsx:2236 ✅ dispatch correct
zod mirror data-display.zod.ts:109 :109type: z.string().optional().describe('Column type')
interface union data-display.ts:255 :255 ✅ unchanged from the card

One correction to the card's own model of the renderer. The card and both rulings treat
NUMERIC_EDIT_TYPES plus a datetime-local branch as the renderer's read set. Measured, the
renderer reads TableColumn.type at exactly one site (:2192editType), feeding
three branches. formatCellValue is type-agnostic (regex on the value). So the live read set
is {date, datetime, datetime-local, number, currency, percent, int, integer, float, double}
— everything else already fell through to the text input before this PR.

3. What happens to an out-of-union field type at the seam (T1)

The seam forwards an object schema's field type, whose vocabulary is @objectstack/spec's
FieldType. Measured against the installed dist: 49 options, of which 42 are outside the
union
; 7 of the 8 declared literals are spec field types and only action is UI-only.
int / integer / float / double / datetime-local are not in the spec enum at all
— the renderer's extra dialect was never a spec vocabulary.

Ruled disposition, and it is total — three outcomes, no fourth:

  1. a canonical spelling passes through unchanged;
  2. a known alias folds onto its canonical spelling (int/integer/float/doublenumber);
  3. anything else yields undefined — the type ANNOTATION is dropped, and the COLUMN IS NEVER DROPPED.

This is where the 42 spec types (select, lookup, user, file, formula, …) land, and
it is behaviour-preserving at the only consumer that reads the key: data-table's editor
branches on date / datetime / the numeric set and otherwise falls through to a text input
— which is exactly the undefined path. The dedicated widget those fields DO get comes from
the host's renderCellEditor, which resolves the field through column.accessorKey and
never reads type (ObjectGrid.tsx:3036). Pinned behaviourally, both halves: the column
still renders its header and cells, and an undeclared type opens the same editor as no type
at all.

⛔ Not folded onto 'text': that would assert something false about a lookup column and
leak the lie into any future reader. Absence says only what is true.

One addition beyond the four aliases the ruling names: datetime-localdatetime.
It is part of the same undeclared renderer dialect (it had its own editor branch at :2273),
so deleting that branch without the fold would silently drop behaviour. Repo precedent is
verbatim — packages/types/src/ui-action.ts:199 already documents datetime-local
datetime as legacy-dialect resolution. Flagged here rather than done quietly.

4. Second-producer census (step 4) — the card named one; there are two

producer writes TableColumn.type? source of the value
plugin-grid/src/ObjectGrid.tsx yes — 5 paths 4 column literals in generateColumns() (:1826, :1915, :1964, :2037) + the fieldDef.type enrichment (:2236), all verbatim
plugin-dashboard/src/ObjectDataTable.tsx yes — the SECOND producer enrich() spreads ...fieldMeta, and buildFieldMeta returns type: overrides.type ?? meta?.type (recordFields.tsx:151) — the raw field type, same defect
plugin-detail/src/RelatedList.tsx ❌ no writes fieldType (a different key) + cell; its type: def.type is inside a FieldMetadata, not a column
plugin-dashboard/DashboardRenderer.tsx:758, DashboardGridLayout.tsx:328 ❌ no pass author options.columns through, else deriveStaticTableColumns which writes no type

Per the ruling ("a second producer found = the same normalization at its seam"),
ObjectDataTable gets the same fold.

Why the ObjectGrid fold is a separate .map() pass rather than folded into the
enrichment map: that map early-returns for _actions and for any column whose accessorKey
has no fieldDef — and a heuristic inferColumnType() value (select, user) rides out on
exactly those columns. Folding inside it would miss them. The seam test deliberately uses an
inline-data grid with no object schema, which is that path.

5. Ablations — direction and count predicted BEFORE running

Both run under the shared verify lock. Both mutations were proved on disk (grep counts of
the injected and removed text in both directions, plus a git hash-object before/after
change) and both restores were proved byte-identical to the HEAD blob, not merely
trusted to the EXIT trap.

Neither needed a rebuild, and that is a measured claim, not an assumption:
vitest.config.mts:261 aliases @object-ui/typespackages/types/src, so these suites
resolve the subject through source, not dist — the stale-dist false-green that makes
an ablation vacuous cannot arise here.

ablation predicted actual match
A — revert the zod tightening to z.string() RED, 9 failures (1 mirror-parity + 1 money + 7 out-of-union refusals) RED, 9 failed / 20 passed ✅ exact
B — delete the ObjectGrid emit-seam fold RED, 4 failures (int, integer, float, double); control number and the select column-survives case still pass RED, 4 failed / 2 passed — exactly those four ✅ exact

Where my prediction was wrong. For ablation B I predicted the unfolded int column would
open a text input; the observed reading was null. Cause: the built-in fallback <Input>
renders with no type attribute rather than type="text", so the probe reads null.
Direction and count were right; the predicted observable was not. The assertion still
separates folded from unfolded, which is what the ablation is for.

6. Gates — each with its own verdict line and exit code

Derived from the CI job step lists under .github/workflows/. All run after the final
commit, against a2bae3d7f, on a clean tree.

gate exit its own verdict line
type-check (types, components, plugin-grid, plugin-dashboard) 0 all four echo type-check: Done; each runs tsc --noEmit && tsc -p tsconfig.test.json, so the new test files are type-checked too
vitest types + plugin-grid + plugin-dashboard 0 Test Files 230 passed (230) · Tests 2277 passed (2277)
vitest components (batch A: renderers/ui/notifications) 0 Test Files 88 passed (88) · Tests 770 passed (770)
vitest components (batch B: src/__tests__) 0 Test Files 103 passed (103) · Tests 968 passed (968)
turbo run lint (4 packages + root) 0 Tasks: 5 successful, 5 total0 errors (685+912+386+244+28 pre-existing warnings, none new: my 9 changed files report errors: 0)
check-changeset-presence.mjs 0 ✅ 9 source file(s) of 4 released package(s) changed, and this change declares 1 changeset(s)
check-changeset-no-major.mjs 0 ✅ No changeset declares a 'major' bump.
check-changeset-fixed.mjs 0 ✅ All workspace packages are in the changeset fixed group.
check-control-bytes.mjs 0 ✅ check-control-bytes: OK (scanned 5241 tracked text file(s))
check:readme-exports 0 ✅ check-readme-exports: OK (… 378 self-imports judged (378 real, 0 wrong-path, 0 fabricated); 0 unbuilt)
check:published-dist 0 ✅ No published package's build output carries tooling material.
check:spec-symbols 0 ✅ spec symbol derivation: 1306 files scanned against 4959 spec export names
check:self-import 0 ✅ No package names itself inside its own src/.
check:esm-specifiers 0 Specifier leg: no un-ledgered package emits an extensionless relative specifier.
check:doc-types 0 ✅ Every documented component type is registered.
check:doc-snippets 0 Semantic phase: 267 of 267 block(s) judged, 0 failed.the narrowing did not break a documented example
check:doc-fences 0 ✅ check:doc-fences — every TypeScript block in 223 document(s) is fenced …
check:phantom-deps 0 ✅ Every in-scope import is declared by the package that publishes it.
lint:coverage 0 ✅ lint coverage: 46/46 packages linted, 0 with outstanding errors
type-check:coverage 0 ✅ test type-check coverage: 41/41 packages compile their tests
check:vi-mock-specifiers 0 ✅ check-vi-mock-specifiers: OK (3757 tracked source file(s))
check:shell-escape-residue 0 ✅ check-shell-escape-residue: OK (4/4 root(s) resolved)
turbo run build --filter='./packages/*' 0 Tasks: 39 successful, 39 total

On the components suite — it ran COMPLETE; the split is batching, not narrowing. A single
run hit the container's ~10-minute foreground cap (exit 143). Rather than narrow coverage, I
split it by directory. Population read from vitest's own config, not from my guess:
vitest list --filesOnly packages/components reports 191 files; batches A + B ran
88 + 103 = 191. (A find glob counted 193 — the two extras are .snap snapshot files,
not tests.)

check:readme-exports was red on first run and that reading was NOT MEASURED, not a
failure
: its own message said type entry ./dist/index.d.ts is not on disk -- run pnpm build first for all 236 items, none of them in a package I touched. After building all 39 packages
it is a real green with 0 unbuilt.

Gates deliberately not run locally, as CI owns them: test:e2e, test:e2e:live:ci,
check:node-esm-load (full build leg; the --specifiers-only leg is green above),
check:i18n-*, check:action-forward-parity, check:designer-field-key-parity,
check:icon-record-names, check:skills-paths, type-check:scripts,
type-check:vitest-setup — no file in this diff is an input to any of them.

7. The pin that stops this regressing (T2)

#5684's guard is key-set only — it checks key presence, and type was present on both
sides the whole time, which is exactly how this instance survived while its siblings were
caught. The value-level pin has two halves:

  • packages/types/src/__tests__/table-column-type-canonical.test.ts — interface tuple ↔ zod
    enum parity, loud refusal (money fails with type in the error path), and
    normalizeTableColumnType totality over all 49 spec field types.
  • packages/components/src/renderers/complex/__tests__/table-column-type-read-set.test.tsx
    the third end neither of those can see: every literal the renderer branches on, derived
    from its source, must be a declared member. Carries blind-spot guards (no switch, no
    includes(editType), exactly one .has(editType)) so a hidden branch fails the instrument
    instead of silently under-counting it.
  • packages/plugin-grid/src/__tests__/columnTypeEmitSeam.test.tsx — the seam, pinned
    behaviourally through the editor a column actually opens, with a control probe.

A future new inference value turning these red is BY DESIGN, and the note at each pin
says so and names the two correct repairs (publish the value across all three ends, or fold it
at the producer) so the next reader does not "fix" it by loosening the mirror.

The zod mirror now builds its z.enum from TABLE_COLUMN_TYPES rather than restating the
members, so the types↔zod leg cannot drift structurally; the pins guard the legs that cannot
be derived.

Changeset

minor for @object-ui/types (published-validator accept-set narrowing + new public API),
patch for the three others. Never majorcheck-changeset-no-major.mjs green above.
The text names the newly-refused spellings in two groups (typos/invented names; object-schema
field types written into a column slot) and states that in-repo authored metadata needs no
migration, with the §1 measurement behind it.


Generated by Claude Code

`TableColumn.type` disagreed three ways: the interface declared 8 literals,
the zod mirror declared `z.string()` and accepted anything, and the renderer
branched on a third set it could only read through an `as any` cast.

Per the maintainer ruling of 2026-08-25 (Option B), the 8-literal interface
union is canonical:

- `TABLE_COLUMN_TYPES` is now the single declaration of the vocabulary, and
  the zod mirror builds its `z.enum` from it, so the two cannot drift.
- Column-inference producers fold their inferred value onto that vocabulary at
  their emit seam (`normalizeTableColumnType`) instead of forwarding an object
  schema's field type verbatim. Two producers do this: `ObjectGrid` and
  `ObjectDataTable`. An out-of-union type drops the `type` annotation — never
  the column.
- The renderer's undeclared dialect (`int`/`integer`/`float`/`double` in
  `NUMERIC_EDIT_TYPES`, the `datetime-local` branch) is gone and the `as any`
  cast with it, so the read is typed against what the interface publishes.

A value-level parity pin covers all three ends; objectui#5684's guard is
key-set only and cannot see value drift.
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 52 chunks) 3223.8 KB 3266.6 KB
Main entry chunk (gzip) 154.1 KB 350 KB
Entry file index-Dk1KuX-U.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 10.96KB 4.16KB
app-shell (runtime-config.js) 18.10KB 6.51KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 505.84KB 114.57KB
core (index.js) 5.30KB 2.13KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 173.18KB 47.97KB
fields (index.js) 238.89KB 60.02KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.44KB 1.39KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 33.40KB 8.71KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.95KB 10.97KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.55KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useResponsiveConfig.js) 1.37KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 9.53KB 3.38KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 4.64KB 1.50KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 1.93KB 0.88KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 46.62KB 12.83KB
plugin-charts (index.js) 64.66KB 18.32KB
plugin-chatbot (index.js) 188.21KB 44.67KB
plugin-dashboard (index.js) 133.46KB 34.48KB
plugin-designer (index.js) 211.95KB 42.75KB
plugin-detail (index.js) 245.10KB 62.31KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 127.52KB 31.01KB
plugin-gantt (index.js) 164.14KB 39.87KB
plugin-grid (index.js) 201.46KB 54.47KB
plugin-kanban (index.js) 52.83KB 14.55KB
plugin-list (index.js) 111.94KB 27.24KB
plugin-map (index.js) 20.09KB 6.62KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 43.49KB 11.93KB
plugin-timeline (index.js) 26.70KB 7.69KB
plugin-tree (index.js) 9.26KB 3.13KB
plugin-view (index.js) 84.55KB 20.74KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 54.84KB 18.43KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.35KB 0.70KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 12.13KB 3.65KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 7.54KB 2.63KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.72KB 2.24KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-support-ai
os-support-ai marked this pull request as ready for review August 25, 2026 18:20
@os-support-ai
os-support-ai added this pull request to the merge queue Aug 25, 2026
Merged via the queue into main with commit fc62bb4 Aug 25, 2026
28 checks passed
@os-support-ai
os-support-ai deleted the claude/issue-5853-tablecolumn-type-canonical-union branch August 25, 2026 18:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant