Skip to content

Make FieldTypes a Map to preserve column order#9279

Merged
manzt merged 2 commits intomainfrom
push-opupxqpylnow
Apr 20, 2026
Merged

Make FieldTypes a Map to preserve column order#9279
manzt merged 2 commits intomainfrom
push-opupxqpylnow

Conversation

@manzt
Copy link
Copy Markdown
Collaborator

@manzt manzt commented Apr 20, 2026

Fixes #9269.

Column names that parse as non-negative integers (e.g. "2000", "2010") were hoisted to the front in numeric order when FieldTypes was a plain Record<string, DataType> since ECMAScript's OrdinaryOwnPropertyKeys iterates integer-indexed keys before string keys.

manzt added 2 commits April 20, 2026 12:11
Fixes #9269.

Column names that parse as non-negative integers (e.g. `"2000"`,
`"2010"`) were hoisted to the front in numeric order when `FieldTypes`
was a plain `Record<string, DataType>` — ECMAScript's
`OrdinaryOwnPropertyKeys` iterates integer-indexed keys before string
keys. `Map` preserves insertion order for all keys and keeps O(1)
lookup.
Copilot AI review requested due to automatic review settings April 20, 2026 16:36
@manzt manzt added the bug Something isn't working label Apr 20, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 20, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Apr 20, 2026 4:36pm

Request Review

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 20, 2026

Bundle Report

Changes will increase total bundle size by 225.11kB (0.9%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
marimo-esm 25.1MB 225.11kB (0.9%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: marimo-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/cells-*.js 59 bytes 704.0kB 0.01%
assets/index-*.js 3.62kB 605.92kB 0.6%
assets/index-*.css 386 bytes 362.67kB 0.11%
assets/JsonOutput-*.js 2.21kB 342.32kB 0.65%
assets/edit-*.js 1 bytes 329.61kB 0.0%
assets/glide-*.js -44 bytes 251.07kB -0.02%
assets/add-*.js 7 bytes 192.76kB 0.0%
assets/layout-*.js -311 bytes 185.6kB -0.17%
assets/cell-*.js 3 bytes 183.15kB 0.0%
assets/reveal-*.js (New) 165.6kB 165.6kB 100.0% 🚀
assets/swiper-*.js (New) 116.4kB 116.4kB 100.0% 🚀
assets/reveal-*.css (New) 54.08kB 54.08kB 100.0% 🚀
assets/file-*.js 50 bytes 49.4kB 0.1%
assets/panels-*.js 6 bytes 45.36kB 0.01%
assets/session-*.js -9 bytes 24.99kB -0.04%
assets/state-*.js 46 bytes 24.45kB 0.19%
assets/home-*.js 132 bytes 21.86kB 0.61%
assets/purify.es-*.js 16 bytes 21.05kB 0.08%
assets/swiper-*.css (New) 15.12kB 15.12kB 100.0% 🚀
assets/run-*.js -735 bytes 9.74kB -7.02%
assets/column-*.js -659 bytes 6.53kB -9.16%
assets/mermaid-*.core-B0EhnNjP.js (New) 2.38kB 2.38kB 100.0% 🚀
assets/slide-*.js (New) 615 bytes 615 bytes 100.0% 🚀
assets/slides-*.css -15.1kB 305 bytes -98.02%
assets/slides-*.js -116.37kB 0 bytes -100.0%
assets/mermaid-*.core-Blvy2Lmj.js (Deleted) -2.38kB 0 bytes -100.0% 🗑️

Files in assets/index-*.js:

  • ./src/plugins/impl/DataEditorPlugin.tsx → Total Size: 5.81kB

Files in assets/JsonOutput-*.js:

  • ./src/components/data-table/column-summary/column-summary.tsx → Total Size: 6.04kB

  • ./src/components/data-table/types.ts → Total Size: 800 bytes

  • ./src/components/data-table/column-summary/chart-spec-model.tsx → Total Size: 13.97kB

Files in assets/glide-*.js:

  • ./src/plugins/impl/data-editor/glide-data-editor.tsx → Total Size: 19.0kB

  • ./src/plugins/impl/data-editor/data-utils.ts → Total Size: 2.42kB

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fix column-order instability for digit-only column names (e.g. "2000", "2010") by changing FieldTypes from a plain object to an ordered Map, avoiding ECMAScript integer-key reordering during iteration.

Changes:

  • Change FieldTypes to Map<string, DataType> and update toFieldTypes accordingly.
  • Update Data Editor and column summary/chart codepaths to use Map APIs (.get, .has, iteration).
  • Update/add Vitest coverage, including a regression test for insertion order with digit-string column names.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
frontend/src/plugins/impl/data-editor/glide-data-editor.tsx Iterates columnFields as a Map and switches lookups to .get/.has.
frontend/src/plugins/impl/data-editor/data-utils.ts Updates modifyColumnFields to operate on Map (insert/remove/rename).
frontend/src/plugins/impl/data-editor/tests/data-utils.test.ts Adjusts fixtures/assertions for Map-based FieldTypes.
frontend/src/plugins/impl/DataEditorPlugin.tsx Initializes columnFields as a Map and converts to Record only for Vega CSV parsing.
frontend/src/components/data-table/types.ts Redefines FieldTypes as Map and updates toFieldTypes.
frontend/src/components/data-table/column-summary/column-summary.tsx Guards stats rendering when type is missing.
frontend/src/components/data-table/column-summary/chart-spec-model.tsx Uses Map.get for fieldTypes and handles missing type when building specs.
frontend/src/components/data-table/tests/types.test.ts Adds regression test ensuring toFieldTypes preserves insertion order for digit-string keys.
frontend/src/components/data-table/tests/chart-spec-model.test.ts Updates test fixtures to use Map-based FieldTypes.

Comment on lines 103 to 107
const columns: ModifiedGridColumn[] = useMemo(() => {
const columns: ModifiedGridColumn[] = [];
for (const [columnName, fieldType] of Object.entries(columnFields)) {
for (const [columnName, fieldType] of columnFields) {
const editable =
editableColumns === "all" || editableColumns.includes(columnName);
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching columnFields to a Map preserves insertion order for columns, but index-based column operations (e.g. delete) can now diverge from removeColumn, which picks the key by Object.keys(row) ordering. For digit-string column names ("2000"), Object.keys will reorder keys, so deleting column N may remove the wrong property from each row. Prefer removing columns by name (derive const columnName = columns[columnIdx].title) and delete that property, or change removeColumn to accept a column name / explicit key order rather than relying on Object.keys.

Copilot uses AI. Check for mistakes.
const { error } = useAsyncData(async () => {
const withoutExternalTypes = toFieldTypes(props.fieldTypes ?? []);

// If we already have the data, return it
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment says "If we already have the data, return it", but the async loader always loads/sets localData and doesn't have an early-return path based on existing state. Either remove the misleading line or implement the intended guard (e.g. skip loading when data is already populated and props.data hasn’t changed).

Suggested change
// If we already have the data, return it
// Use the provided rows directly when `props.data` is already an array.

Copilot uses AI. Check for mistakes.
Comment on lines +116 to 118
const newEntries: Array<[string, DataType]> = [
...entries.slice(0, columnIdx),
[newColumnName, dataType || "string"],
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the rename case, if dataType is omitted this code sets the renamed column’s type to "string". However BulkEdit.Rename edits (applied in glide-data-editor.tsx) don’t include a dataType, so rehydrating/applying initial rename edits will silently change the column’s type (e.g. number -> string). Consider preserving the existing type at columnIdx when dataType is undefined, only overriding when an explicit dataType is provided.

Suggested change
const newEntries: Array<[string, DataType]> = [
...entries.slice(0, columnIdx),
[newColumnName, dataType || "string"],
const existingDataType = entries[columnIdx]?.[1];
const newEntries: Array<[string, DataType]> = [
...entries.slice(0, columnIdx),
[newColumnName, dataType ?? existingDataType ?? "string"],

Copilot uses AI. Check for mistakes.
@manzt manzt merged commit d075706 into main Apr 20, 2026
34 of 35 checks passed
@manzt manzt deleted the push-opupxqpylnow branch April 20, 2026 20:34
@github-actions
Copy link
Copy Markdown

🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.23.2-dev63

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Columns named with string rep of integers (eg "2010") ALWAYS appearing before other columns

3 participants