Commit graph

2 commits

Author SHA1 Message Date
b4c0327f63 feat(tables): row editor — inline Add Row, Delete, multi-row paste, min row height
The cell-editor was already complete (drafts, row-blur saves, etag
concurrency, validation). This commit adds the missing row-level ops:

- "+ Add row" appends a draft row inline; first cell focused. Row-blur
  POSTs to <dir>/form.html (the existing form-create endpoint); 201
  swaps the synthetic id for the server-returned URL/ETag. Empty rows
  the user walks away from are silently discarded.
- Right-click a row → "Delete row" (or "Delete N rows" when a cell
  range spans multiple rows). DELETE the row YAML with If-Match; 412
  surfaces a conflict warning.
- Multi-row clipboard paste creates new rows for grid content that
  extends past the last existing row, instead of dropping cells past
  the end. Each new row saves via its own row-blur.
- Empty rows now have a 2.4em minimum height so a freshly-added row
  is visible. Without the floor it collapses to cell-padding (~8px)
  and looks like a divider line.

Server-side: no new endpoints. Form-create (POST <dir>/form.html →
201 + Location) and file-API DELETE carry the new client capabilities.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 16:07:28 -05:00
8e703dc61a feat(tables): editable cells phase 4 — copy/paste from Excel/Sheets
Bidirectional clipboard interop with Excel, Google Sheets, and any
other spreadsheet that uses RFC-4180-ish TSV on the text/plain
clipboard mime. Pasted cells write straight into the draft buffer
the same way per-key edits do; row-level save (Phase 3) picks them
up on the next row-blur with the same If-Match optimistic-
concurrency flow.

TSV parser (clipboard.js parseTSV):

- Tabs separate columns, \\n / \\r\\n separate rows.
- Quoted fields ("...") may contain tabs and newlines verbatim.
- Doubled \\"\\" inside a quoted field escapes a literal \\".
- Trailing empty row from a final \\n is dropped (Excel sends
  this; matching the convention avoids a phantom blank row at
  the end of every paste).

Apply-paste (clipboard.js applyPaste):

- Anchor = currently selected cell.
- 1×1 clipboard into selection → writes that one cell.
- N×M clipboard → SPILLS from the anchor down/right to
  (anchor.row + N - 1, anchor.col + M - 1). Cells past the end
  of either axis are silently dropped with a toast count.
- Each pasted value goes through coerceCell, which checks the
  column's row-schema property type:
    * number / integer → Number()
    * boolean          → "true"|"yes"|"1" → true; "false"|
                         "no"|"0"|""      → false
    * everything else  → raw string
  Drafts hold the right JS type so the row-PUT body matches the
  JSON Schema the server validates against.

Copy (clipboard.js onCopy):

- Single-cell selection: Ctrl/Cmd+C writes the cell's
  effectiveCellValue (draft if dirty, else stored) as text/plain
  via formatCell (RFC-4180 quoting on tab/newline/quote).
- Range copy is Phase 5 (depends on range-selection landing).

Event wiring:

- document.addEventListener('paste'/'copy') so events bubble
  from any cell with focus. Phase 1's roving tabindex moves
  focus around; per-cell binding would have to be re-applied
  after every paint.
- onPaste bails when an editor input is mounted (the input
  owns its own paste — typing into a cell editor that was just
  populated with a chunk of TSV would be a footgun).

Toast for partial pastes:

When applyPaste skipped any cells, a small message in
#table-status: "Pasted N cells; M dropped (out of bounds)".
Auto-clears after 4s. Coexists with Phase 3's stale-row prompt
(toast doesn't fire if a prompt is already up; prompt outranks
toast).

Tests (6 new Phase 4 specs, total 37 in tests/tables.spec.js):

- parseTSV handles tabs, newlines, and quoted fields — covers
  the parser edge cases including embedded \\n inside "..." and
  doubled "" escapes.
- paste single value into selected cell — the 1×1 path; verifies
  the draft buffer entry.
- paste 2×2 grid spills from anchor — the N×M spill semantic.
- paste coerces numeric/boolean values via row schema —
  verifies the draft holds typeof===number for an integer column
  and === true for a boolean column.
- paste out-of-bounds drops cells silently with toast — drives
  via dispatched ClipboardEvent('paste') (the only way to
  exercise onPaste end-to-end including the toast).
- copy single cell writes value to clipboard — synthesizes a
  ClipboardEvent('copy') with a writable DataTransfer payload
  and asserts the cell value lands in text/plain.

Bundle size: 134 KB → 138 KB.

Files:

- tables/js/clipboard.js (new) — parseTSV, formatTSV,
  applyPaste, onPaste/onCopy, toast helper.
- tables/build.sh — clipboard.js in concat list.
- zddc/internal/handler/tables.html — regenerated bundle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 10:30:05 -05:00