Skip to content

feat(line-items): document-line editing component + bulk picker, perf, polish#238

Draft
itsprade wants to merge 6 commits intomainfrom
pradeep/line-items-component
Draft

feat(line-items): document-line editing component + bulk picker, perf, polish#238
itsprade wants to merge 6 commits intomainfrom
pradeep/line-items-component

Conversation

@itsprade
Copy link
Copy Markdown
Contributor

@itsprade itsprade commented May 6, 2026

Summary

Introduces the LineItems family — a spreadsheet-grade table for editing rows of a document (POs, sales invoices, journals, work orders, stock transfers) — plus the supporting useLineItems hook and a generic BulkItemPicker dialog.

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> compoundTable, SearchToggle, FullscreenToggle, BulkActions, AddRow, SaveActions, Search, TotalsRow, FloatingDock, DirtyBar, SelectionBar.
  • useLineItemsGroup({...}) — composes multiple useLineItems hooks 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 to addLine. N picks → one render / one logical changeset op.

Behaviour

  • Range selection (click + shift-click + drag), fill-drag, TSV copy/paste, single-cell broadcast paste.
  • Arrow nav, Enter/Tab edge wrapping, Esc-revert.
  • Pinned columns (pinned: "left" | "right"), flex column, hover-expand columns, row-end actions slot (rowActions), totals row.
  • Fullscreen overlay with backdrop click + Escape; backdrop fade + card slide-up on open.
  • Floating dock pattern (denim-tears-style): auto-mounting DirtyBar (with optional warnOnNav for in-app + beforeunload interception) and SelectionBar (render-prop for consumer actions).
  • Skeleton loader: opt-in loading prop for initial fetches + automatic shimmer during fast-scroll-flicks (CSS-only; input focus survives the flick).

Per-module demos (examples/app-module)

Path Module Showcases
/custom-page/line-items-demo Purchase Order full feature surface — bulk picker, sidebar mode controls, row actions, pinned cols
/custom-page/sales-invoice-demo Sales Invoice LineItems.TotalsRow
/custom-page/goods-receipt-demo Goods Receipt pinned: "left" columns + per-cell className for discrepancy highlight
/custom-page/work-order-demo Work Order useLineItemsGroup with heterogeneous row types (components vs operations)
/custom-page/stock-transfer-demo Stock Transfer rowActions slot + cross-field validation in Save
/custom-page/journal-entry-demo Journal Entry useLineItemsGroup (debits + credits sharing one submit)

Perf wins this branch

  • Cells commit on blur (not per keystroke). 1,200-row typing went from laggy to instant.
  • rowActions routes through a stable ref so the column memo isn't busted on every render — fixed inputs dropping focus after the first character.
  • Demo removed a per-keystroke 1,200-row useEffect sync; total column computed live in render.
  • Confirmed virtualization (only ~32 of 1,200 rows mount at any time).

Bug fixes

  • Shift-click no longer bleeds OS text-selection across editable cells in between.
  • Fullscreen modal close is instant; open uses a soft cubic-bezier ease-out.

PRD alignment

  • ChangeSet shape: { isEmpty, lineChanges: [{action: "add" | "update" | "remove" | "reorder", tempId | lineId | position, ...}] } — matches the platform PRD's transport-agnostic spec.
  • The component remains UI-only — no GraphQL/REST coupling, no opinion on submit transport.

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 build for @tailor-platform/app-shell and app-module
  • Run pnpm dev and walk the six demo pages:
    • PO demo: bulk picker opens, search filters tree, parent toggle selects all variants, "Add N items" commits in one render
    • PO demo: type fast in any editable cell — no lag, no focus loss
    • PO demo: shift-click range across editable cells — no native text-selection bleed
    • PO demo: copy one cell + select range + paste → broadcasts; copy a 2×3 block → rectangular paste
    • PO demo: fullscreen toggle animates on open, instant close
    • PO demo: first ~600ms shows skeleton rows; rapid scroll-flick shows pulse skeleton until idle
    • PO demo: Mode and demo controls live in the right-column ActionPanel
    • Sales Invoice: TotalsRow updates live as Qty / Rate / Discount / Tax change
    • Goods Receipt: SKU + Product stay pinned during horizontal scroll; mismatched Received highlights red
    • Stock Transfer: trailing actions column visible; cross-field validation surfaces an error on Save
    • Work Order + Journal Entry: two stacked tables share one Save / Discard via the group hook

Out of scope (deliberate)

  • Promoting Skeleton to a top-level UI primitive (only used inside LineItems for now).
  • Wiring BulkItemPicker into the other module demos (only line-items-demo lights it up; rest still alert(...) until a real consumer asks).
  • Field validation surface — workarounds documented in the Obsidian note; will revisit when a consumer needs it.
  • Column resize / reorder / show-hide.

🤖 Generated with Claude Code

itsprade and others added 5 commits May 6, 2026 04:20
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>
@itsprade itsprade marked this pull request as draft May 6, 2026 14:06
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>
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 6, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@tailor-platform/app-shell@238
npm i https://pkg.pr.new/@tailor-platform/app-shell-sdk-plugin@238
npm i https://pkg.pr.new/@tailor-platform/app-shell-vite-plugin@238

commit: 4f548d9

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.

1 participant