Skip to content

Column paths, a shared column layout, and a row label for datasets - #9

Merged
chkn merged 8 commits into
mainfrom
claude/project-thread-xmzah6
Oct 13, 2026
Merged

chkn merged 8 commits into
mainfrom
claude/project-thread-xmzah6

Conversation

@chkn

@chkn chkn commented Oct 12, 2026

Copy link
Copy Markdown
Owner

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 › title works. 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 on main, since #8 (named resource instances) is already merged there.

How:

  • Q.1 Paths. src/shared/column-paths.ts adds ColumnPath, readColumnPath and resolveColumnPath, replacing the grid's CellPath/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.
  • Q.2 Finding paths. describeRows adds instances (each instance's resource, row count and merged argument names). The new DatasetProvider.describePath lists 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 shared resolveColumnPath, so the server and grid always agree on references. The route is POST …/paths.
  • Q.3 Layout (server). Dataset.columns and rowLabel are stored in a new nullable layout column on datasets. One migration covers both providers, since the local one wraps Turso. setColumns is served by PATCH …/columns, which checks that every root exists in the schema or some row. deleteField prunes the field's columns and the row label. A column of an unknown kind is stored as written.
  • Q.3 Layout (client). reconcileColumns (dataset-columns.ts) fits the stored list to the dataset:
    • It drops gone fields and keys, and appends new ones.
    • It dedupes paths and keeps unknown kinds in place as dimmed "Unknown column" placeholders.
    • A dataset with no columns looks exactly as it does today.
  • Q.3 Grid and picker. canMoveColumn backs onColumnProposeMove: a key column stays in its own group, and nothing lands inside an expanded group. movedLayout writes one setColumns on drop. The picker reuses the trace list's popover, with a live-swap drag that is also written once on drop.
  • Q.4 Editing. Through an added column, an argument holding a plain string, number or boolean is editable. The edit replaces that instance in the update's resources. A field's key columns stay read-only, as P.2 has them.
  • Tests.
    • Unit tests cover each step rule, two references, the hop limit, "n/a", reconciliation (dropped, appended, duplicate, no columns), moves and argument edits.
    • Provider contract tests cover instances, describePath, setColumns and pruning, with handler tests for validation.
    • Playwright CT covers an accepted header drag, a vetoed one, editing a referenced argument inline with the row label, the picker, and an unknown column round-tripping.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QMiMTviyLp3ciCvQf3mzkf


Generated by Claude Code

…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
@chkn chkn self-assigned this Oct 12, 2026
Comment thread src/client/components/dataset-columns.ts Outdated
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 chkn left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

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:

  1. 🔴 Layout deadlock. Once no row declares an instance that a column or the row label is rooted at, every PATCH …/columns returns 400. That includes the two edits that would fix it (handleSetColumns).
  2. 🟡 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

Comment thread src/server/handlers/datasets.ts Outdated
Comment thread src/client/components/dataset-grid.ts
Comment thread src/client/components/DatasetColumnPicker.tsx Outdated
Comment thread src/server/handlers/datasets.ts Outdated
Comment thread src/shared/column-paths.ts
Comment thread src/client/components/dataset-columns.ts Outdated
claude and others added 3 commits October 12, 2026 21:16
- 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
@chkn
chkn marked this pull request as ready for review October 13, 2026 09:40
@chkn
chkn merged commit 32d08e1 into main Oct 13, 2026
1 check passed
@chkn
chkn deleted the claude/project-thread-xmzah6 branch October 13, 2026 10:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants