Skip to content

Fix/misc calc improvements#20

Open
stefnnn wants to merge 8 commits into
tinycld:mainfrom
stefnnn:fix/misc-calc-improvements
Open

Fix/misc calc improvements#20
stefnnn wants to merge 8 commits into
tinycld:mainfrom
stefnnn:fix/misc-calc-improvements

Conversation

@stefnnn
Copy link
Copy Markdown
Contributor

@stefnnn stefnnn commented Jun 1, 2026

Here's another maxi-PR with some fixes and improvements for the calc app:

fix: misc fixes

  1. Delete key should only clear content, not formatting
  2. Multi-select header tint — selecting multiple rows/cols only tints the first header green
  3. Top-left corner cell should select all
  4. Paste Special → "Format only" should include number/cell format and colors as well
  5. Paste Special → "Format only" keyboard shortcut doesn’t work as advertised
  6. Paste doesn't work on Safari

feat: format paintbrush
Adds a new toolbar button that copies formatting (text+colors+number format) and then applies it to a click/drag target, with Excel-style row-major source→destination mapping and wrap. Also works for whole rows / columns.

review comments and fixes
Follow-up cleanups on the format-painter feature surfaced in review:

  1. Paintbrush cursor was a no-op. The painter set a --calc-grid-cursor
    CSS variable on that no rule ever consumed, so the cursor never
    changed when the painter was armed. Replace it with a
    calc-format-painter-active class toggled on , and add a rule in
    the overlay's injected stylesheet that applies an Excel-style paintbrush
    cursor (inline SVG data-URI, white outline halo + blue bristle tip,
    hotspot at the tip, copy system fallback). The !important + *
    selector overrides the per-cell cursors.

  2. Apply logic was duplicated three ways. The "expand a single-cell target
    to the source dimensions, then tile" logic lived inline in Cell onPress,
    the Cell PanResponder onDragEnd, and Grid's applyFormatPainterIfActive.
    Extract it into applyFormatPainterToDest() in style-helpers and route all
    three call sites through it. Header clicks always produce multi-cell
    selections, so unifying the header path is behavior-preserving.

readCellStyle's potential Y.Doc reference aliasing was reviewed and left
as-is: the re-apply goes through setYCellStyle (a deep-merge patch), so it
is safe in practice regardless.

stefnnn and others added 6 commits June 1, 2026 10:12
1. Delete key should only clear content, not formatting
2. Multi-select header tint — selecting multiple rows/cols only tints the first header green
3. Top-left corner cell should select all
4. Paste Special → "Format only" should include number/cell format and colors as well
5. Paste Special → "Format only" keyboard shortcut doesn’t work as advertised
Adds a new toolbar button that copies formatting (text+colors+number format) and then applies it to a click/drag target, with Excel-style row-major source→destination mapping and wrap. Also works for whole rows / columns.
Follow-up cleanups on the format-painter feature surfaced in review:

1. Paintbrush cursor was a no-op. The painter set a `--calc-grid-cursor`
   CSS variable on <html> that no rule ever consumed, so the cursor never
   changed when the painter was armed. Replace it with a
   `calc-format-painter-active` class toggled on <html>, and add a rule in
   the overlay's injected stylesheet that applies an Excel-style paintbrush
   cursor (inline SVG data-URI, white outline halo + blue bristle tip,
   hotspot at the tip, `copy` system fallback). The `!important` + `*`
   selector overrides the per-cell cursors.

2. Apply logic was duplicated three ways. The "expand a single-cell target
   to the source dimensions, then tile" logic lived inline in Cell onPress,
   the Cell PanResponder onDragEnd, and Grid's applyFormatPainterIfActive.
   Extract it into applyFormatPainterToDest() in style-helpers and route all
   three call sites through it. Header clicks always produce multi-cell
   selections, so unifying the header path is behavior-preserving.

readCellStyle's potential Y.Doc reference aliasing was reviewed and left
as-is: the re-apply goes through setYCellStyle (a deep-merge patch), so it
is safe in practice regardless.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The first paintbrush-only cursor read as an ugly blob. Replace it with the
classic Excel format-painter glyph: a bold white cross (the click target,
hotspot at its center) with a small paintbrush to the right. White fills
with a black outline so it stays legible on any cell background.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fill the unit-test gaps for the recent fixes and the format-painter
feature — all pure / Y.Doc-level functions, no React harness needed:

1. applyFormatPainterStyles / applyFormatPainterToDest — row-major modulo
   tiling, the single-cell→source-size expansion, and multi-cell targets
   left un-expanded.
2. clearYCellContent — wipes value fields while preserving the style map,
   and removes the entry entirely when the cell has no style.
3. Clipboard numFmt round-trip — guards that Paste → Format only carries
   number format (and colors) through the HTML encode/decode path.
4. selectAll store action — scope/anchor/range and empty-grid clamp, the
   action the corner-cell click depends on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The HTML clipboard decoder stored colors sans `#` (e.g. `FF0000`) while
the color picker writes `#`-prefixed hex (e.g. `#B00020`) to font.color /
fill.fgColor. Both render fine via normalizeColor, but the forms aren't
byte-identical, so swatch active-matching (ColorPickerGrid compares with
`selected === swatch.hex`) and any color equality break for pasted cells.

Make parseCssColor emit canonical `#RRGGBB` uppercase for both hex and
rgb() inputs, matching the picker. Update the decode tests accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@nathanstitt nathanstitt left a comment

Choose a reason for hiding this comment

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

This looks great! I don't feel strongly about any of the comments so up to you if you want to update or not

Comment thread tinycld/calc/components/Grid.tsx Outdated
import type { WorkbookFileActions } from '../hooks/use-workbook-file-actions'
import { useAllYSheets, useYSheets } from '../hooks/use-y-sheets'
import { classifyCellKey } from '../lib/cell-key-action'
import type { CellStyle } from '../lib/workbook-types'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is where CI's Typecheck & Unit job is probably failing , biome probably doesn't like that workbook-types sorts after conditional-format/a1 (line 36), so Biome wants this line moved down. Same import-ordering issue in components/grid/Body.tsx (the FormatPainterOverlay import) and hooks/grid-store.ts (the CellStyle import).

It slipped through locally because npx tinycld-pkg check runs only tsc + vitest — Biome runs only ecosystem-wide via npm run lint from app/, which is what member CI invokes. One-liner fix from the workspace root:

cd ~/code/tinycld/app && npm run lint:fix

then commit the reformatted files.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ah darn, I don't get myself around to installing biome. When I tried the above, then it failed because of a minor version mismatch (2.4.15 against 2.4.16) and I realized, that there's no package-lock.json anywhere. I assume that's due to the bootstrapped install from the workspace root? Isn't that a significant security risk these days? Fixed the linting in the end 👍

Comment thread tinycld/calc/components/grid/style-helpers.ts
Comment thread tinycld/calc/hooks/use-calc-shortcuts.ts Outdated
Comment thread tinycld/calc/components/grid/RowHeader.tsx
@stefnnn stefnnn force-pushed the fix/misc-calc-improvements branch 2 times, most recently from aa0613b to fe052cf Compare June 1, 2026 20:19
@stefnnn stefnnn force-pushed the fix/misc-calc-improvements branch from fe052cf to 2c034d1 Compare June 1, 2026 20:39
This addresses a Safari paste failure. The root cause was that Safari rejects the async navigator.clipboard.read()/readText() when invoked from a keydown-driven tinykeys shortcut (after any
  await, the user-gesture context is lost and permission is required). The fix:

  1. Removes the $mod+v tinykeys shortcut (use-calc-shortcuts.ts) so the browser fires its native paste event instead of swallowing the key.
  2. Adds a native paste listener in Grid.tsx that reads event.clipboardData synchronously — no permission, works in all browsers.
  3. readFromClipboardEvent mirrors the existing async readFromOsClipboard logic exactly (html → marker → fidelity store → fallback to tsv), so paste fidelity is preserved.
@stefnnn
Copy link
Copy Markdown
Contributor Author

stefnnn commented Jun 1, 2026

@nathanstitt I implemented the suggested suggestions and tacked one more fix on top: pasting content in calc was actually never working on Mac, because of the strict way that Safari handles native paste events (must be directly invoked by a user action, a minor await will kill that, which seems what tinykeys is doing).

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