-
Notifications
You must be signed in to change notification settings - Fork 2
Feat/excel m4 structural #326
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
c35e455
feat(sheet): sheet-list, dimension and freeze ops (Go)
SamTV12345 a4ad30f
feat(sheet): TS mirror for sheet-list, dimension and freeze ops
SamTV12345 32dfce6
feat(sheet): M4 UI — 200x52 grid, resize, freeze, tabs, sort, filter
SamTV12345 a262cb8
test(sheet): E2E for tabs, resize persistence, freeze, sort, filter
SamTV12345 1c3d824
fix(sheet): address M4 review findings
SamTV12345 94b7c82
chore: retrigger GitHub mergeability check after rebase
SamTV12345 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| # M4 — Structural / grid polish (multiple sheets, resize, freeze, sort/filter) | ||
|
|
||
| Implements the M4 section of `docs/superpowers/specs/2026-07-01-excel-spreadsheet-design.md`. | ||
| Branch `feat/excel-m4-structural`, stacked on `fix/sheet-typing-ws` (PR #324). | ||
|
|
||
| ## Ops (Go `lib/sheet` + TS mirror) | ||
|
|
||
| - **Sheet list**: `addSheet` (sheet=new id, name, index), `renameSheet`, `deleteSheet`, | ||
| `moveSheet` (toIndex). Convergence rules: duplicate add = first-wins no-op, deleting | ||
| the last sheet = no-op, and **any op on a missing sheet is a silent no-op** (a late op | ||
| after a concurrent `deleteSheet` must not poison the ordered-log replay — this changed | ||
| `Apply`'s old unknown-sheet error). | ||
| - **`setDimension`** (axis `col`/`row`, index, sizePx 1..4096): sparse | ||
| `ColWidths`/`RowHeights` maps on `Sheet`; structural insert/delete shifts the maps | ||
| (in-band deletes drop overrides); `Transform` shifts `setDimension.Index` on the | ||
| matching axis. | ||
| - **`setFreeze`** (frozenRows/frozenCols, 0|1): per-sheet metadata; arbitrary freeze is | ||
| the upgrade path. | ||
| - Snapshot round-trips all new metadata (`colWidths`/`rowHeights` JSON objects with | ||
| stringified indices, `frozenRows`/`frozenCols`). | ||
|
|
||
| ## View / UI | ||
|
|
||
| - Grid grown to **200×52** (`ponytail:` DOM-node-per-cell; virtualization is the upgrade | ||
| path, comment in `sheetView.ts`). | ||
| - **Resize**: drag grips on the header cells (`.sheet-resizer-col/-row`), live preview, | ||
| one `setDimension` op on mouseup. Column overrides also relax the per-cell | ||
| `min-width: 80px` default. | ||
| - **Freeze**: `border-collapse: separate` (sticky drops collapsed borders) with | ||
| right/bottom-only 1px borders; `.sheet-frozen-r/-c` classes + `--fr-top`/`--fc-left` | ||
| CSS vars measured at render. | ||
| - **Tabs bar** (`sheetTabs.ts`): click switch, dblclick rename (native prompt), | ||
| right-click delete (native confirm, disabled for the last sheet), HTML5 drag reorder, | ||
| `+` add. The client-local filter resets on sheet switch. | ||
| - **Sort** (`sheetSortFilter.ts`): A→Z / Z→A toolbar buttons sort the selected range by | ||
| the focused column as a batch of `setCell` ops; moved formulas shift row refs via the | ||
| fill heuristic (`adjustFormula`). Numbers sort numerically, empties always last. | ||
| - **Filter**: toolbar dropdown of the focused column's distinct values; hides | ||
| non-matching rows client-side (blank rows stay visible). Not collaborative in v1 — | ||
| collaborative filter is the upgrade path. | ||
|
|
||
| ## Testing | ||
|
|
||
| - Go: `lib/sheet/structural_test.go` (apply/validate/transform/convergence/snapshot, | ||
| dim shifting). `TestApplyUnknownSheet` now asserts the no-op semantics. | ||
| - TS: `structural.test.ts` (op mirror), `sheetSortFilter.test.ts` (sort batch, formula | ||
| shift, distinct/hide predicates). | ||
| - E2E: `playwright/specs/sheet_structural.spec.ts` — tabs add/switch/rename with | ||
| per-sheet data isolation, column resize persisting across reload, sticky frozen row, | ||
| A→Z sort, filter hide/clear. Ran live 5/5 green (plus the 10 existing sheet specs). | ||
|
|
||
| ## Known ceilings (deliberate) | ||
|
|
||
| - Filter: one active column filter, client-local. | ||
| - Freeze: first row / first col only. | ||
| - No virtualization; 200×52 is the practical grid bound for the DOM view. |
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,9 @@ | ||
| package sheet | ||
|
|
||
| import "sort" | ||
| import ( | ||
| "maps" | ||
| "sort" | ||
| ) | ||
|
|
||
| // CellSnapshot is the serializable form of one populated cell (map keys can't | ||
| // be JSON-encoded, so cells become a flat slice). | ||
|
|
@@ -17,6 +20,11 @@ type SheetSnapshot struct { | |
| Id string `json:"id"` | ||
| Name string `json:"name"` | ||
| Cells []CellSnapshot `json:"cells"` | ||
| // Sparse dimension overrides; JSON object keys are stringified indices. | ||
| ColWidths map[int]int `json:"colWidths,omitempty"` | ||
| RowHeights map[int]int `json:"rowHeights,omitempty"` | ||
| FrozenRows int `json:"frozenRows,omitempty"` | ||
| FrozenCols int `json:"frozenCols,omitempty"` | ||
| } | ||
|
|
||
| // WorkbookSnapshot is the JSON-serializable form of a Workbook for persistence. | ||
|
|
@@ -40,7 +48,16 @@ func (w *Workbook) Snapshot() WorkbookSnapshot { | |
| } | ||
| return cells[a].Col < cells[b].Col | ||
| }) | ||
| out.Sheets[i] = SheetSnapshot{Id: s.Id, Name: s.Name, Cells: cells} | ||
| ss := SheetSnapshot{Id: s.Id, Name: s.Name, Cells: cells, FrozenRows: s.FrozenRows, FrozenCols: s.FrozenCols} | ||
| // Clone: snapshots are consumed after the document lock is released | ||
| // (export), so aliasing the live maps would race with Apply(). | ||
| if len(s.ColWidths) > 0 { | ||
| ss.ColWidths = maps.Clone(s.ColWidths) | ||
| } | ||
| if len(s.RowHeights) > 0 { | ||
| ss.RowHeights = maps.Clone(s.RowHeights) | ||
| } | ||
|
Comment on lines
+51
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2. Snapshot shares mutable maps Workbook.Snapshot() stores ColWidths/RowHeights map references directly in the snapshot, so the snapshot can be mutated after sheetdoc.Manager.Snapshot() unlocks but before sendSheetVars marshals, creating an inconsistent snapshot relative to the returned head revision. Agent Prompt
|
||
| out.Sheets[i] = ss | ||
| } | ||
| return out | ||
| } | ||
|
|
@@ -66,6 +83,9 @@ func WorkbookFromSnapshot(snap WorkbookSnapshot) *Workbook { | |
| for _, c := range ss.Cells { | ||
| sh.Cells[CellRef{c.Row, c.Col}] = Cell{Raw: c.Raw, Value: c.Value, ValueType: c.ValueType, StyleId: c.StyleId} | ||
| } | ||
| maps.Copy(sh.ColWidths, ss.ColWidths) | ||
| maps.Copy(sh.RowHeights, ss.RowHeights) | ||
| sh.FrozenRows, sh.FrozenCols = ss.FrozenRows, ss.FrozenCols | ||
| w.Sheets[i] = sh | ||
| } | ||
| return w | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.