Repository navigation
Column paths, a shared column layout, and a row label for datasets - #9
Conversation
…ferences Generalizes the grid's one-level CellPath into ColumnPath (specs/datasets.md §Q.1): a field or instance root and any number of keys, each step taken by what it stands on, including an instance reference's arguments. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QMiMTviyLp3ciCvQf3mzkf
describeRows now summarizes the rows' resource instances, and describePath lists the keys one level inside any column path, walking each row by readColumnPath's rules (specs/datasets.md §Q.2). A dataset gains columns and rowLabel metadata, written by setColumns (PATCH .../columns) into a new nullable layout column; deleteField prunes the field's columns (§Q.3). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QMiMTviyLp3ciCvQf3mzkf
reconcileColumns fits a stored layout to the dataset's fields and rows, appending what's new, dropping what's gone and keeping unknown kinds in place (specs/datasets.md §Q.3). The grid's columns now follow it, including added columns, with the move rules header drags need and inline editing of an instance's argument through an added column (§Q.4). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QMiMTviyLp3ciCvQf3mzkf
The dataset view gains a Columns button whose popover shows, hides, reorders and removes columns, discloses a field's keys, adds a column from a tree of the rows' paths, and sets the row label. Headers can be dragged, with moves that would split a field's keys vetoed, and their menu gains Hide column and Use as row label. The details pane names a row by its label, and an added column ending at an instance's argument is typed into in place (specs/datasets.md §Q.3, §Q.4). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QMiMTviyLp3ciCvQf3mzkf
fieldEntry already returns a field's existing entry, so the lookup before it was redundant. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QMiMTviyLp3ciCvQf3mzkf
chkn
left a comment
There was a problem hiding this comment.
Overall this is well structured. Sharing resolveColumnPath between the server and the grid, reconcileColumns, and how canMoveColumn/movedLayout handle hidden columns all read cleanly. On the head commit, npm run typecheck passes, as do the unit and provider tests that cover this change (7 files, 210 tests). The only check CI runs on this PR is reuse, so those tests aren't run there.
Two behaviour bugs, both reproduced with a scratch test:
- 🔴 Layout deadlock. Once no row declares an instance that a column or the row label is rooted at, every
PATCH …/columnsreturns 400. That includes the two edits that would fix it (handleSetColumns). - 🟡 Clearing an argument in place. The argument is deleted, and the cell then goes read-only (
groupEdits).
There's also one smaller UX gap: removing the row label's column leaves no way to clear the label. The rest are elegance suggestions: a narrowing guard that removes two casts, moving instance-name validation into parseColumnPath alongside a pathInstance helper, and restructuring a branch in reconcileColumns.
Generated by Claude Code
- A layout whose instance no row declares any more can still be saved: instance roots the stored layout or row label already has are accepted, and only new ones are looked up in the rows. - Clearing an instance argument in the grid is a no-op rather than deleting it, which left the cell read-only. - Removing the row label's column clears the label in the same write, and "Stop using as row label" now actually clears it (saveColumns' default parameter swallowed the explicit undefined). - parseColumnPath checks instance names; isPathColumn and pathInstance replace casts and inline root checks; reconcileColumns splits on the root first. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QMiMTviyLp3ciCvQf3mzkf
Hiding the last column from its header menu, with the pointer left over one of its cells, crashed the grid: Glide's cursor check reads the stale hovered cell on the redraw that drops the column, and cellView dereferenced the missing column. A column past the end now shows empty. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QMiMTviyLp3ciCvQf3mzkf
Requested by Alex · project thread
Before: the dataset grid showed every field in schema order, plus the keys one level inside a field. A row whose identity sat behind a resource reference, like asgard's planTask task title, looked the same as every other row. Column order and visibility couldn't be changed, and rows were only ever named by their number.
After: a Columns button in the dataset header shows, hides, reorders and removes columns, and can add a column at any path in the rows. A path can step through instance references, so
toolsContext › create_task › rootTaskId › titleworks. Headers can be dragged, and their menu gains "Hide column" and "Use as row label". The details pane reads "Row 3 · Web site for Evalution". The layout and row label are dataset metadata, shared and synced, while widths and expansion stay per viewer. An added column that ends at an instance's argument can be typed into in place.This implements
specs/datasets.md§Q. It's based onmain, since #8 (named resource instances) is already merged there.How:
src/shared/column-paths.tsaddsColumnPath,readColumnPathandresolveColumnPath, replacing the grid'sCellPath/readPath. Paths stop after 16 reference hops. They serialize to ids that keep today's width keys (fieldId,fieldId/key), with an instance root written as@name.describeRowsaddsinstances(each instance's resource, row count and merged argument names). The newDatasetProvider.describePathlists the keys one level in, each with a sample. A JSONB query picks the rows with something at the root, and each row is then walked with the sharedresolveColumnPath, so the server and grid always agree on references. The route isPOST …/paths.Dataset.columnsandrowLabelare stored in a new nullablelayoutcolumn ondatasets. One migration covers both providers, since the local one wraps Turso.setColumnsis served byPATCH …/columns, which checks that every root exists in the schema or some row.deleteFieldprunes the field's columns and the row label. A column of an unknownkindis stored as written.reconcileColumns(dataset-columns.ts) fits the stored list to the dataset:columnslooks exactly as it does today.canMoveColumnbacksonColumnProposeMove: a key column stays in its own group, and nothing lands inside an expanded group.movedLayoutwrites onesetColumnson drop. The picker reuses the trace list's popover, with a live-swap drag that is also written once on drop.resources. A field's key columns stay read-only, as P.2 has them."n/a", reconciliation (dropped, appended, duplicate, nocolumns), moves and argument edits.instances,describePath,setColumnsand pruning, with handler tests for validation.🤖 Generated with Claude Code
https://claude.ai/code/session_01QMiMTviyLp3ciCvQf3mzkf
Generated by Claude Code