Skip to content

P1: formatting sandbox — full CellFormat, layout tools, style recipes, NL coverage - #44

Merged
Ocean82 merged 2 commits into
mainfrom
p1-formatting-sandbox
Sep 27, 2026
Merged

Ocean82 merged 2 commits into
mainfrom
p1-formatting-sandbox

Conversation

@Ocean82

@Ocean82 Ocean82 commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner

The formatting half of the "useful first, unique second" strategy, stacked on #43. Same executor spine as the existing agent tools — no new agent runtime.

19 files, +983 / −4.

P1.1 — format_cells reaches the full CellFormat surface

Underline, strikethrough, fontFamily, textAlign, verticalAlign, textWrap and borders, wired through buildFormatPatch, FormatCellsParams and the tool registry schema. src/lib/formatCellsTool.ts owns patch construction.

P1.2 — layout tools

set_column_width, set_row_height and auto_fit as agent tools, with ExecutionContext hooks delegating to the existing undoable store methods (src/agent/toolHandlers/layoutOps.ts, src/store/aiExecution.ts).

P1.3 — NL coverage

parseLayoutPhrase routes column-width / row-height / auto-fit requests through both the client parser and the server actTemplates, sharing one phrase table (shared/spreadsheetPhrases.ts) so the two can't drift. Includes a false-positive corpus.

P1.4 — style recipes

Bounded one-shot macros — header, total_row, table_polish — resolved to a plan that drives both the Apply/Reject preview and execution, so what the user reviews is exactly what runs. Reuses the existing formatAsTable / generateTableTotals building blocks rather than re-implementing table styling.

style_recipe joins DESTRUCTIVE_TOOLS, so "Should I add a total row?" no longer fires a bulk restyle.

Fixes included

Two pre-existing breakages that were masked by an unresolved merge on the original branch:

  • styleRecipes.test.ts — used refToCell's inverse wrongly (cellToRef takes a cell id, not row/col)
  • styleRecipes.ts — dead totalsRow binding, which failed lint:ci (--max-warnings=0)

Verification

Gate Result
npm run lint:ci pass
npm run typecheck pass
npm run test 1822 / 1822
npm run test --prefix server 344 / 344
npm run test:realengine 12 / 12

Notes for the reviewer

  • parser.ts and previewBuilders.ts also carry the P0: share gap-risk detection with the preview path; user-entered AI feedback #43 changes, since the P1 branches are appended to the same functions. Review the P1-only hunks (the parseLayoutPhrase / parseStyleRecipePhrase blocks and the style_recipe preview branch).
  • The P0 gate (strategy doc §3, steps 1–6) is a manual smoke test that CI cannot verify — tracked in docs/verification-backlog.md items 1–3.

🤖 Generated with Claude Code

Summary by Sourcery

Extend the formatting sandbox with comprehensive cell formatting, undoable layout controls, natural-language coverage, and previewable style recipes.

New Features:

  • Expand cell formatting to cover typography, alignment, wrapping, and borders.
  • Add agent tools for setting column widths, setting row heights, and auto-fitting rows.
  • Add natural-language parsing for layout commands and bounded header, totals, and table-polish style recipes.
  • Provide shared preview and execution plans for style recipes, including totals formulas and table filters.

Bug Fixes:

  • Prevent style recipe totals from stacking or including a previously generated totals row when reapplied.
  • Keep ordinary bold-header and format-as-table requests routed to their existing tools.

Enhancements:

  • Reuse existing table-formatting and totals functionality for consistent style recipe behavior.
  • Add undo-aware execution hooks for spreadsheet layout mutations.
  • Mark style recipes as destructive so they require confirmation before execution.

Tests:

  • Add coverage for layout phrase parsing, style recipe parsing, tool routing, formatting patches, layout handlers, recipe planning, idempotent totals, and preview changes.

Copilot AI lite review requested due to automatic review settings September 27, 2026 18:00
@sourcery-ai

sourcery-ai Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

This PR completes the formatting sandbox by extending format_cells, adding undo-aware layout tools, sharing NL phrase parsing across client and server paths, and introducing bounded style recipes whose preview and execution consume the same resolved plan.

Sequence diagram for shared natural-language formatting actions

sequenceDiagram
    actor User
    participant Parser as ClientParser
    participant Templates as ActTemplates
    participant Phrases as SharedPhraseTable
    participant Preview as PreviewBuilder
    participant Executor as ToolExecutor
    participant Store as SpreadsheetStore

    User->>Parser: parseMessage(request)
    Parser->>Phrases: parseLayoutPhrase(request)
    Parser->>Phrases: parseStyleRecipePhrase(request)
    Parser-->>User: tool action
    User->>Templates: resolveActTemplates(request)
    Templates->>Phrases: parseLayoutPhrase(request)
    Templates->>Phrases: parseStyleRecipePhrase(request)
    Templates-->>User: Apply action
    User->>Preview: buildActionPreview(style_recipe)
    Preview->>Executor: buildRecipePlan(recipe)
    Executor-->>User: preview changes
    User->>Executor: executeTool(action)
    Executor->>Store: setColumnWidth / setRowHeight / autoFitRows
    Executor->>Store: apply recipe plan
Loading

File-Level Changes

Change Details Files
Expanded cell formatting to cover the remaining CellFormat properties and normalize border input.
  • Added underline, strikethrough, font family, horizontal and vertical alignment, text wrapping, and border parameters to the types and tool schema.
  • Centralized validation and conversion of formatting parameters in buildFormatPatch, including enum checks and all-side border expansion.
  • Added focused tests for the new formatting surface and invalid/empty border inputs.
shared/toolRegistry.ts
shared/toolTypes.ts
src/lib/formatCellsTool.ts
src/lib/formatCellsTool.test.ts
Added agent-facing layout operations backed by store-level spreadsheet layout mutations.
  • Registered handlers for column width, row height, and row auto-fit tools.
  • Implemented parsing for single values, ranges, comma lists, 1-based row references, clamping/delegation behavior, populated-row detection, and single history points.
  • Added ExecutionContext hooks that update column widths, row heights, and auto-fit results through existing layout utilities.
  • Covered layout parsing and handler success/error paths with unit tests.
shared/toolRegistry.ts
src/agent/executor.ts
src/agent/toolHandlers/index.ts
src/agent/toolHandlers/layoutOps.ts
src/agent/toolHandlers/layoutOps.test.ts
src/store/aiExecution.ts
Unified natural-language parsing for layout and style-recipe requests across client and server execution paths.
  • Added shared parsers for explicit and relative width/height, auto-fit, and bounded header/total-row/table-polish phrases.
  • Routed the shared phrases through client parseMessage and server actTemplates, with tests for expected routes and false positives.
  • Marked style_recipe as destructive so it participates in confirmation handling without hijacking plain formatting or table requests.
shared/spreadsheetPhrases.ts
shared/spreadsheetPhrases.test.ts
shared/actTemplates.ts
src/agent/parser.ts
src/agent/parser.gaps.test.ts
src/store/slices/chatSlice.ts
Implemented bounded style recipes with a shared execution and preview plan.
  • Added header, total_row, and table_polish recipe planning based on detected table ranges and existing table-formatting/total-generation primitives.
  • Applied recipe plans through cell writes, format updates, and filters while preserving one history point.
  • Used the same plan to generate Apply/Reject preview changes, including formula additions and touched-cell accounting.
  • Added recipe validation, handler behavior, and plan/preview tests.
src/lib/styleRecipes.ts
src/lib/styleRecipes.test.ts
src/agent/toolHandlers/formatOps.ts
src/lib/previewBuilders.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 8f5c3382-38b8-4a5a-9c61-db49d6719e8f


Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've reviewed your changes and they look great!

Sourcery assessment

Needs a human reviewer. If the table detection or total-row logic is wrong, applying the recipe can write a Total row and SUM formulas into the workbook, while styling, filters, and layout changes also persist after the code is reverted. These effects are bounded and can be undone or manually recomputed and removed, but reverting the PR alone will not repair workbooks already changed.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Base automatically changed from p0-usefulness-first to main September 27, 2026 18:24
@Ocean82

Ocean82 commented Sep 27, 2026

Copy link
Copy Markdown
Owner Author

Review follow-up: total_row double-counting — found and fixed in 7dd3a65

The data-integrity concern raised in review was valid, and checking it turned up a concrete corruption bug rather than just a theoretical risk.

Applying the total_row recipe twice silently doubled the reported total. detectTableRange derives endRow from every non-empty cell, so the Total row written by the first run became part of the "data":

writes formula reported total
1st run A5 B5 C5 =SUM(B2:B4) 4
2nd run A6 B6 C6 =SUM(B2:B5) 8

The second run's range spans the first run's total, and it compounded on every re-apply. The preview rendered a plausible-looking Total row each time, so nothing looked wrong — the number was just quietly inflated.

The recipe now recognises its own output (the Total label and at least one SUM formula on the row) and treats that row as the target, excluding it from the data, so re-running refreshes it in place. Requiring the formula as well as the label is load-bearing: a data row that merely reads "Total" is left alone and the totals row is appended below it. I got that wrong on the first attempt and a test caught it.

Detection is confined to styleRecipes.ts — generateTableTotals's only caller — so formatAsTable.ts is untouched and the helper remains a pure function of the range it receives.

On the revert-doesn't-repair-workbooks point: worth noting these writes are already undoable in-app. handleStyleRecipe calls ctx.pushHistory(...) before mutating (formatOps.ts:37), and style_recipe is in the highImpactTools set in chatSlice.ts, so it goes through the Apply/Reject preview path like the other bulk mutations. A user who applies a bad recipe has a single undo. The residual risk is narrow: a recipe applied via a saved workbook and then re-opened in a session where undo no longer spans it.

Also checked and not affected: header and table_polish are format-only and re-applying overwrites rather than accumulates, so they're naturally idempotent.

Verification

Gate Result
npm run lint:ci pass
npm run typecheck pass
npm run test 1825 / 1825 (+3 regression)
npm run test --prefix server 344 / 344
npm run test:realengine 12 / 12

🤖 Generated with Claude Code

…, NL coverage

The formatting half of the "useful first, unique second" strategy, stacked on
p0-usefulness-first. Same executor spine as the agent tools — no new runtime.

P1.1 — format_cells reaches the full CellFormat surface
  Underline, strikethrough, fontFamily, textAlign, verticalAlign, textWrap and
  borders, wired through buildFormatPatch, FormatCellsParams and the tool
  registry schema. src/lib/formatCellsTool.ts owns the patch construction.

P1.2 — layout tools
  set_column_width, set_row_height and auto_fit as agent tools, with
  ExecutionContext hooks delegating to the existing undoable store methods
  (src/agent/toolHandlers/layoutOps.ts, src/store/aiExecution.ts).

P1.3 — NL coverage
  parseLayoutPhrase routes column-width / row-height / auto-fit requests
  through both the client parser and the server actTemplates, sharing one
  phrase table (shared/spreadsheetPhrases.ts) so the two can't drift.

P1.4 — style recipes
  Bounded one-shot macros — header, total_row, table_polish — resolved to a
  plan that drives both the Apply/Reject preview and execution, so what the
  user reviews is what runs. style_recipe joins the question-veto set, so
  "Should I add a total row?" no longer fires a bulk restyle.

Verified: lint:ci, typecheck, 1822 unit tests, 344 server tests,
12 realengine golden-set tests.
Applying the total_row recipe twice silently doubled the reported total.

detectTableRange derives endRow from every non-empty cell, so the Total row
written by the first run became part of the "data". The second run then
appended a *new* Total row one row lower and widened the SUM range to span the
first one:

  1st run   A5="Total"  B5==SUM(B2:B4)   -> total 4
  2nd run   A6="Total"  B6==SUM(B2:B5)   -> total 8, and B5 is now double-counted

It compounded on every re-apply, and the preview showed the user a plausible
looking "Total" row each time, so nothing looked wrong.

Now the recipe recognises its own output — the Total label *and* at least one SUM
formula on the row — and treats that row as the target, excluding it from the
data. Re-running refreshes the same row in place. Requiring the formula as well
as the label matters: a data row that merely reads "Total" is left alone and the
totals row is appended below it instead.

Detection is confined to styleRecipes.ts, which is generateTableTotals' only
caller, so formatAsTable.ts is untouched and the helper stays a pure function
of the range it is handed.

Verified: lint:ci, typecheck, 1825 unit tests, 344 server tests,
12 realengine golden-set tests. Adds 3 regression tests covering repeat
application, the "Total" data row, and refresh after a value change.
@Ocean82
Ocean82 force-pushed the p1-formatting-sandbox branch from 7dd3a65 to 4e38414 Compare September 27, 2026 18:40
@Ocean82
Ocean82 merged commit c4bf3e6 into main Sep 27, 2026
5 checks passed
@Ocean82
Ocean82 deleted the p1-formatting-sandbox branch September 27, 2026 19:20
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