feat(line-items): document-line editing component + bulk picker, perf, polish#238
Draft
feat(line-items): document-line editing component + bulk picker, perf, polish#238
Conversation
Introduces a complete `LineItems` component family for editing rows of a
document (purchase orders, sales orders, invoices) with spreadsheet
ergonomics — keyboard navigation, range selection, fill drag, TSV
copy/paste, virtualized rendering, and dirty tracking.
Public API (`@tailor-platform/app-shell`):
- `useLineItems()` hook — state + mutations + change-set + revert/reset
- `LineItems.{Root, Table, Search, SearchToggle, BulkActions, AddRow,
FullscreenToggle, SaveActions}` compound parts
- `createLineItemHelper<T>().field({...})` for type-safe field schemas
- Field-level options: `width`, `hoverExpandWidth` for column sizing
Core pieces:
- Virtualized table on `@tanstack/react-virtual` (newly added dep) +
`@tanstack/react-table` for column defs / sorting
- Spreadsheet behaviors implemented in `spreadsheet-logic.ts`
(rectangular cells, paste TSV, fill drag, arrow nav)
- Dirty-tracking baseline in `useLineItems`; `revert()` restores the
baseline, `reset()` snaps it forward
- `LineItems.Root` owns the fullscreen overlay + Escape-to-close +
click-outside-to-close, with descendant CSS rules to stretch a hosted
Card.Root in fullscreen
- `LineItems.SearchToggle` — icon button that expands inline into a
search input
- Cell shell paints selection ring via `box-shadow: inset` so the ring
matches cell bounds pixel-perfectly across stickiness/repaint
- Native number-spinner suppression hoisted into `inputBaseClasses` so
every shared input behaves consistently
Demo (`examples/app-module`):
- `line-items-demo.tsx` — full Purchase-Order page with:
- Card.Header title/description + action cluster
[Search][Import from CSV][Bulk add][Expand]
- Edge-to-edge virtualized table (1200 seeded rows)
- 4-side-padded inline add-row Combobox (SKU + product two-line)
- Floating bottom-center action dock with dirty + selection bars
(mirrors the denim-tears bulk-export pattern)
- Jiggle animation + click/anchor + beforeunload guards while dirty
- Reusable `<LineItemsSection initialData={…}>` exported and dropped
into the 2-column layout demo with `[]` so the empty/build-from-zero
state is exercised
- Resource registered in `custom-module.tsx`
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…er-module demos
Visual & UX polish:
- Cell selection ring, hover-expand columns, edge-to-edge table inside Card
- Search toggle, fullscreen toggle with click-outside-close
- Shift-click range, fill drag, copy/paste fixes
- Header checkbox centering, pinned columns, row-end actions, totals row
- Flex column with min-width floor; trailing spacer absorbs leftover space
Library additions (all opt-in, non-breaking):
- LineItems.FloatingDock + DirtyBar + SelectionBar (denim-tears floating bar pattern)
- lineItemsFloatingBarStyles helpers for matching button chrome
- warnOnNav prop intercepts in-app links + beforeunload + jiggle animation
- LineItems.TotalsRow render-prop for sticky footer
- pinned: 'left' | 'right' on fields
- rowActions slot on LineItems.Table
- useLineItemsGroup helper for multi-collection documents
- equals / normalize / amend readonly tint / expanded field types
PRD alignment:
- ChangeSet shape: {isEmpty, lineChanges: [{action, tempId|lineId|position, ...}]}
Per-module demos (examples/app-module):
- sales-invoice-demo (totals row)
- goods-receipt-demo (pinned columns)
- stock-transfer-demo (row actions + cross-field validation)
- work-order-demo + journal-entry-demo (multi-collection via group hook)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…st paste - Fullscreen modal slides + fades in on open (backdrop 220ms, card translate-from-below 280ms cubic-bezier). Close is instant. - Shift-click range no longer bleeds OS text-selection across editable cells in between: blur active editor, removeAllRanges, focus grid container. - Single-cell paste now broadcasts to every cell in the active selection (Excel parity), skipping read-only columns. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New surfaces: - BulkItemPicker (top-level) — generic tree-select dialog with tri-state checkboxes, search, auto-expand, "Add N" CTA. Decoupled from LineItems. - useLineItems().addLines(items) — N rows in one render / one changeset op. - LineItems.Table loading + skeletonRowCount props for initial fetches; an automatic fast-scroll skeleton swap during scroll-flicks (CSS-only, no React re-render so input focus survives). Perf: - Text/number/date cells commit on blur, not per keystroke (1,200×-ish reduction in writes during typing). - rowActions routes through a stable ref so the column memo isn't busted on every render — fixes input focus dropping after first character. - Demo removed a 1,200-row sync useEffect; total column is computed live. Bug fixes: - Shift-click no longer bleeds OS text-selection across editable cells. - Single-cell paste now broadcasts to a multi-cell selection (Excel parity). Polish: - Fullscreen modal: backdrop fade + card slide-up on open. Demo wiring: - line-items-demo: CATALOG extended with variants on two SKUs, BulkItemPicker wired to both Bulk-add buttons, loading state with 600ms timer, mode toggles moved to a sidebar ActionPanel, note column replaced with rowActions column. Verification: type-check, lint, 731 tests passing, build clean for both @tailor-platform/app-shell and the app-module example. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the convention in docs/components/ — every public surface gets a markdown reference page with frontmatter, an Import block, props tables, and common pairings. LineItems doc covers: useLineItems options + return shape, full field schema + all five field type kinds, every compound part with prop tables, modes, pinned columns, multi-collection group hook, change-set shape, skeleton loader, spreadsheet behaviours, tips. BulkItemPicker doc covers: all eleven props + node shape, selection model, search behaviour, keyboard, common pairings (addLines, async catalog, custom filter button), tips. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolved conflicts: - packages/core/src/index.ts — kept both LineItems exports and main's new Collection / DataTable / CollectionControlProvider exports. - packages/core/package.json — kept @tanstack/react-table and react-virtual deps (still required by LineItems.Table). - examples/nextjs-app/src/modules/custom-module.tsx — kept the six new line-items demo route imports + nav links alongside main's data-table-demo. - pnpm-lock.yaml — regenerated against the merged dependency set. Demo files (line-items-demo, sales-invoice-demo, goods-receipt-demo, stock-transfer-demo, work-order-demo, journal-entry-demo) auto-relocated from examples/app-module/src/pages/ to examples/nextjs-app/src/modules/pages/ via git's rename detection — no content changes needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Introduces the
LineItemsfamily — a spreadsheet-grade table for editing rows of a document (POs, sales invoices, journals, work orders, stock transfers) — plus the supportinguseLineItemshook and a genericBulkItemPickerdialog.This branch is the first land of the component, wrapped together with several iterations of polish, six per-module demos under
examples/app-module, and a perf pass that takes typing in a 1,200-row demo from "feels laggy" to "feels instant."What's in here
New library surfaces (all
minor, additive, non-breaking)useLineItems<T>(options)— owns rows, baseline, dirty-tracking, change-set, selection, filter, ordering, mode (edit | display | amend).<LineItems.Root>compound —Table,SearchToggle,FullscreenToggle,BulkActions,AddRow,SaveActions,Search,TotalsRow,FloatingDock,DirtyBar,SelectionBar.useLineItemsGroup({...})— composes multipleuseLineItemshooks under one document-level submit boundary (Journal Entry debits + credits, Work Order components + operations).BulkItemPicker<T>— generic tree-select dialog (tri-state checkboxes, substring search with auto-expand, "Add N" CTA). Decoupled from LineItems; reusable for any multi-select-from-tree flow.useLineItems().addLines(items)— batch insert sibling toaddLine. N picks → one render / one logical changeset op.Behaviour
pinned: "left" | "right"),flexcolumn, hover-expand columns, row-end actions slot (rowActions), totals row.DirtyBar(with optionalwarnOnNavfor in-app +beforeunloadinterception) andSelectionBar(render-prop for consumer actions).loadingprop for initial fetches + automatic shimmer during fast-scroll-flicks (CSS-only; input focus survives the flick).Per-module demos (
examples/app-module)/custom-page/line-items-demo/custom-page/sales-invoice-demoLineItems.TotalsRow/custom-page/goods-receipt-demopinned: "left"columns + per-cellclassNamefor discrepancy highlight/custom-page/work-order-demouseLineItemsGroupwith heterogeneous row types (components vs operations)/custom-page/stock-transfer-demorowActionsslot + cross-field validation in Save/custom-page/journal-entry-demouseLineItemsGroup(debits + credits sharing one submit)Perf wins this branch
rowActionsroutes through a stable ref so the column memo isn't busted on every render — fixed inputs dropping focus after the first character.useEffectsync;totalcolumn computed live in render.Bug fixes
PRD alignment
{ isEmpty, lineChanges: [{action: "add" | "update" | "remove" | "reorder", tempId | lineId | position, ...}] }— matches the platform PRD's transport-agnostic spec.Test plan
pnpm type-check(currently green)pnpm lint— 0 warnings, 0 errors (currently green)pnpm test— 731 passing, no skips (was 722 pre-iteration)pnpm buildfor@tailor-platform/app-shellandapp-modulepnpm devand walk the six demo pages:Out of scope (deliberate)
Skeletonto a top-level UI primitive (only used inside LineItems for now).BulkItemPickerinto the other module demos (onlyline-items-demolights it up; rest stillalert(...)until a real consumer asks).🤖 Generated with Claude Code