@sheetpane/grid 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,188 +1,271 @@
1
1
  # sheetpane — the full public contract
2
2
 
3
- > Everything llms.txt links to, inlined: the package READMEs, the docs, the laws and the component records. Version 0.1.0; links pinned to v0.1.0.
3
+ > Everything llms.txt links to, inlined: the package READMEs, the docs, the laws and the component records. Version 0.3.0; links pinned to v0.3.0.
4
4
 
5
5
 
6
6
  ---
7
7
 
8
- <!-- source: README.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/README.md) -->
8
+ <!-- source: README.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/README.md) -->
9
9
 
10
- # sheetpane
10
+ <div align="center">
11
11
 
12
- **Open, edit and review `.xlsx` and `.csv` files inside any web page — without
13
- uploading them anywhere.**
12
+ <picture>
13
+ <source media="(prefers-color-scheme: dark)" srcset="docs/readme/logo-dark.svg">
14
+ <img src="docs/readme/logo-light.svg" alt="sheetpane" width="460">
15
+ </picture>
14
16
 
15
- Two web components, `<sheetpane-excel>` (the sheet) and `<sheetpane-errors>`
16
- (what's wrong with it), stay in sync through one in-memory workbook. Download
17
- hands back a copy of the original file with your changes written into it.
17
+ ### Open, edit and review `.xlsx` and `.csv` files inside your web app.<br>Nothing is uploaded. Download gives back the original file, with only your edits patched in.
18
+
19
+ [![CI](https://github.com/arpitarya/sheetpane/actions/workflows/ci.yml/badge.svg)](https://github.com/arpitarya/sheetpane/actions/workflows/ci.yml)
20
+ [![MIT licence](https://img.shields.io/badge/licence-MIT-2346C8)](LICENSE)
21
+ [![TypeScript](https://img.shields.io/badge/types-TypeScript-2346C8)](packages/grid/README.md#typings-and-editor-support)
22
+ [![Web components](https://img.shields.io/badge/works%20with-any%20framework-2346C8)](#works-where-you-work)
23
+
24
+ [**Playground**](https://arpitarya.github.io/sheetpane/) ·
25
+ [**Get started**](#get-started) ·
26
+ [**Docs**](packages/grid/README.md) ·
27
+ [**Starters**](examples/starters/README.md) ·
28
+ [**Changelog**](CHANGELOG.md)
29
+
30
+ <br>
31
+
32
+ <picture>
33
+ <source media="(prefers-color-scheme: dark)" srcset="docs/readme/screenshot-dark.png">
34
+ <img src="docs/readme/screenshot-light.png" alt="A holdings workbook open in sheetpane: four cells flagged by a rules file, listed with the expected value in the errors panel beside the sheet" width="100%">
35
+ </picture>
36
+
37
+ <sub>A real capture, not a mock-up: <code>sample.xlsx</code> checked by <a href="docs/readme/holdings.rules.json">a 9-line rules file</a>. Regenerate it with <code>node docs/readme/capture.mjs</code>.</sub>
38
+
39
+ </div>
40
+
41
+ ---
42
+
43
+ ## Three lines of HTML
18
44
 
19
45
  ```html
20
46
  <script type="module">import '@sheetpane/grid';</script>
21
- <sheetpane-excel pane="q3" src="holdings.xlsx" mode="edit"></sheetpane-excel>
47
+
48
+ <sheetpane-excel pane="q3" src="holdings.xlsx" mode="edit" rules-src="rules.json"></sheetpane-excel>
22
49
  <sheetpane-errors pane="q3"></sheetpane-errors>
23
50
  ```
24
51
 
52
+ **That is the whole integration.** A spreadsheet, an error list, kept in sync by
53
+ the shared `pane` id — put them side by side, in tabs, or in a dialog.
54
+
25
55
  ---
26
56
 
27
- ## Status — read this first
57
+ ## Why sheetpane
28
58
 
29
- **sheetpane is young.** The first release, `0.1.0`, is being published; until
30
- it is on npm, the install commands below will not work yet. Until 1.0, the API
31
- may change between minor versions; the [CHANGELOG](CHANGELOG.md) says when it
32
- does.
59
+ | | |
60
+ |---|---|
61
+ | 🔒 **Private by construction** | The file is read in the browser tab. There is no server to send it to. |
62
+ | 📄 **The file survives the round trip** | Untouched → back **byte for byte**. Edited → the original with only the changed cells patched in. |
63
+ | ✅ **Review built in** | Errors are flagged on the cell *and* listed beside the sheet — from a JSON rules file, your own column rules, or the workbook's own validation. |
64
+ | ⚡ **Big files stay smooth** | A 250,000-row CSV scrolls with **0 dropped frames** (measured in headless Chromium on a MacBook — not a promise for your hardware). |
65
+ | 🧩 **Pay for what you use** | Formulas, styles, charts, images and more are opt-in plugins — you ship only what you import. |
66
+ | 🎨 **Looks like your app** | No font of its own — it inherits yours. Light, dark or auto. |
33
67
 
34
- **What is tested.** Each feature below has tests in this repo, run in Node and
35
- in a real browser: Chromium on every change, Chromium, Firefox and WebKit
36
- nightly. The two demos are driven end to end in Chromium, also from the packed
37
- tarballs npm would publish.
68
+ ---
38
69
 
39
- **What is not yet proven** — honest gaps, tracked in
40
- [`work/OPEN-WORK.md`](work/OPEN-WORK.md):
70
+ ## Fix it in place
41
71
 
42
- - **Files saved by real apps.** The reader is tested against generated and
43
- library-written files, not yet against files saved by Excel, Google Sheets,
44
- LibreOffice and Numbers (W-30).
45
- - **The round trip in Excel.** Exported files are read back by independent
46
- libraries in CI; opening them in desktop Excel is still a manual check.
47
- - **Phones.** Touch selection and download are built, but have not been
48
- checked on a real iPhone.
72
+ Edit a flagged cell and its error resolves in the panel — every change held in
73
+ memory until you download.
49
74
 
50
- If you hit a file that opens wrongly, an issue with the file attached (or a
51
- minimal copy of it) is the most useful thing you can send.
75
+ <p align="center">
76
+ <img src="docs/readme/demo.gif" alt="Typing a missing advisor, a positive unit count and an approved fee rate; each error in the panel turns green as resolved" width="100%">
77
+ </p>
78
+
79
+ The rules behind it are [this JSON file](docs/readme/holdings.rules.json), with a [schema](packages/grid/README.md#rules-file) your editor completes:
80
+
81
+ ```json
82
+ {
83
+ "version": 1,
84
+ "sheets": { "Holdings": { "headerRow": 2, "columns": {
85
+ "Advisor": { "required": true },
86
+ "Units": { "type": "integer", "min": 0 },
87
+ "Fee rate": { "oneOf": ["0.40%", "0.75%", "0.85%"] },
88
+ "As of": { "type": "date", "min": "2026-09-01", "max": "2026-09-30", "severity": "warning" }
89
+ } } }
90
+ }
91
+ ```
52
92
 
53
93
  ---
54
94
 
55
- ## What it does
95
+ ## Get started
56
96
 
57
- - **Reads in the browser tab.** Nothing is sent to a server; there is no server.
58
- - **View or edit.** Typing, paste, undo/redo, range selection, find, zoom,
59
- freeze panes, resize, hidden rows/columns/sheets — the spreadsheet basics.
60
- - **Review.** Errors are flagged on cells and listed beside the sheet: from
61
- your own column rules, a JSON rules file, or the workbook's own validation.
62
- - **Download keeps the file.** An untouched file comes back byte for byte; an
63
- edited one is the original with only the changes patched in.
64
- - **Large files stay usable.** A 250,000-row CSV scrolls without dropped frames
65
- in headless Chromium on a MacBook — a measurement, not a guarantee for your
66
- hardware.
67
- - **Any framework, or none.** Plain HTML, React, Vue, Svelte, Solid, Angular;
68
- typings and editor data are generated for each.
69
- - **Your look.** No font of its own; light, dark or auto theme.
97
+ > [!NOTE]
98
+ > **`0.1.0` is about to ship.** Until it is on npm, the install commands and the
99
+ > playground links will not work yet.
70
100
 
71
- ## What it does not do
101
+ ```sh
102
+ npm install @sheetpane/grid
103
+ ```
72
104
 
73
- **XLSX and CSV only — by design** ([law L3](records/0005_LAW-3-xlsx-and-csv-only.md)).
74
- `.xls`, `.xlsm`, `.xlsb`, `.ods` and `.numbers` are refused with a clear message,
75
- never half-read.
105
+ Then follow the [`@sheetpane/grid` guide](packages/grid/README.md). Or try it
106
+ first: the [playground](https://arpitarya.github.io/sheetpane/), its
107
+ [React version](https://arpitarya.github.io/sheetpane/react/), or a
108
+ [starter in StackBlitz](examples/starters/README.md).
76
109
 
77
- - **No macros**, and macro-enabled workbooks are refused.
78
- - **No password-protected files** — they are detected and refused, not unlocked.
79
- - **No real-time collaboration** and no storage: the workbook lives in memory;
80
- saving it somewhere is your app's job.
81
- - **Pivot tables** are shown as their values with a badge, not recalculated.
110
+ ---
111
+
112
+ ## Works where you work
113
+
114
+ The elements are standard web components, so they render in any framework — or
115
+ none. **No wrapper packages**; one `import type` line gives typed tags, props
116
+ and events.
117
+
118
+ | | |
119
+ |---|---|
120
+ | **Plain HTML** | `import '@sheetpane/grid'` — no build step needed |
121
+ | **React 19 / 18** | typed JSX, plus [copy-in hooks](packages/grid/README.md#react-hooks-recipes) (`useSelection`, `useErrors`, `useChanges`…) |
122
+ | **Vue · Svelte · Solid · Angular** | [one line of setup each](packages/grid/README.md#vue-svelte-solid-angular) |
123
+ | **Your tests** | [`@sheetpane/testing`](packages/testing/README.md) — locators and actions for Playwright and Vitest |
124
+ | **Your AI agent** | [skills](packages/skills/README.md) for Claude Code, Kiro and Codex · a read-only [MCP server](packages/mcp/README.md) · [`llms.txt`](llms.txt) |
82
125
 
83
126
  ---
84
127
 
85
- ## Get started
128
+ ## Plugins
86
129
 
87
- **A new app:**
130
+ **Opt-in, one import each.** Ship only what your users need.
88
131
 
89
- ```sh
90
- npm create sheetpane@latest
132
+ | plugin | adds |
133
+ |---|---|
134
+ | [`numfmt`](packages/numfmt/README.md) | every Excel number format, with locales |
135
+ | [`formulas`](packages/formulas/README.md) | recalculation as you edit — IronCalc, WebAssembly, lazy-loaded |
136
+ | [`styles`](packages/styles/README.md) | fills, fonts, borders, alignment, merges |
137
+ | [`condfmt`](packages/condfmt/README.md) | conditional formatting, colour scales, data bars |
138
+ | [`validation`](packages/validation/README.md) | the workbook's own data-validation rules |
139
+ | [`charts`](packages/charts/README.md) | bar, column, line, pie, doughnut and scatter |
140
+ | [`images`](packages/images/README.md) | pictures anchored in the sheet |
141
+ | [`comments`](packages/comments/README.md) | notes and threaded comments, read and written |
142
+ | [`visibility`](packages/visibility/README.md) | hide, unhide and group rows, columns and sheets |
143
+
144
+ ```html
145
+ <script type="module">import '@sheetpane/grid'; import '@sheetpane/formulas/ui';</script>
146
+ <sheetpane-excel src="model.xlsx" plugins="formulas"></sheetpane-excel>
91
147
  ```
92
148
 
93
- Vanilla, React, Next.js, Vue, Angular or Svelte — with both elements, a sample
94
- file, a rules file and one test ([`create-sheetpane`](packages/create-sheetpane/README.md)).
149
+ ---
95
150
 
96
- **An existing app:**
151
+ ## The editing basics, included
97
152
 
98
- ```sh
99
- npm install @sheetpane/grid
100
- ```
153
+ Typing and paste · undo/redo · range selection with Count · Sum · Average ·
154
+ find · zoom · freeze panes · resize rows and columns · hidden rows, columns and
155
+ sheets · a pop-up mode · keyboard and touch.
156
+
157
+ ---
158
+
159
+ ## What it deliberately does not do
101
160
 
102
- Then follow the [`@sheetpane/grid` README](packages/grid/README.md).
161
+ **XLSX and CSV only** ([law L3](records/0005_LAW-3-xlsx-and-csv-only.md)).
162
+ Anything else is refused with a clear message — never half-read.
103
163
 
104
- **Try it first:** the [live playground](https://arpitarya.github.io/sheetpane/)
105
- (plain HTML) and its [React version](https://arpitarya.github.io/sheetpane/react/),
106
- or a [starter in StackBlitz](examples/starters/README.md). These go live with
107
- the first release.
164
+ | not supported | what happens |
165
+ |---|---|
166
+ | `.xls`, `.xlsm`, `.xlsb`, `.ods`, `.numbers` | refused, with the reason |
167
+ | macros, password-protected files | detected and refused |
168
+ | real-time collaboration, storage | the workbook lives in memory; saving it is your app's job |
169
+ | pivot tables | shown as their values, with a badge — not recalculated |
170
+
171
+ ---
172
+
173
+ ## How far it is proven
174
+
175
+ **Every feature has tests**, in Node and a real browser: Chromium on every
176
+ change; Chromium, Firefox and WebKit nightly. Both demos are driven end to end —
177
+ also from the packed tarballs npm would publish.
178
+
179
+ <details>
180
+ <summary><b>The honest gaps</b> — tracked in <a href="work/OPEN-WORK.md"><code>OPEN-WORK</code></a></summary>
181
+
182
+ <br>
183
+
184
+ - **Files saved by real apps.** The reader is tested on generated and
185
+ library-written files; not yet on files saved by Excel, Google Sheets,
186
+ LibreOffice and Numbers (W-30).
187
+ - **The round trip in desktop Excel.** Exports are read back by independent
188
+ libraries in CI; opening them in Excel is still a manual check.
189
+ - **Phones.** Touch selection and download are built, not yet checked on a real
190
+ iPhone.
191
+
192
+ Until 1.0 the API may change between minor versions — the
193
+ [CHANGELOG](CHANGELOG.md) says when.
194
+
195
+ </details>
196
+
197
+ **Found a file that opens wrongly?** [Open an issue](https://github.com/arpitarya/sheetpane/issues/new)
198
+ with the file (or a minimal copy) attached — it is the most useful thing you can send.
108
199
 
109
200
  ---
110
201
 
111
202
  ## Packages
112
203
 
113
- **One version for all**, ESM with types. Plugins are opt-in, so you only ship
114
- what you use.
204
+ **One version for all**, ESM with types.
115
205
 
116
206
  | package | what it is |
117
207
  |---|---|
118
208
  | [`@sheetpane/grid`](packages/grid/README.md) | the two web components, the pane's workbook, the plugin host |
119
209
  | [`@sheetpane/import`](packages/import/README.md) | `.xlsx` / `.csv` in, a plain JavaScript workbook out |
120
210
  | [`@sheetpane/export`](packages/export/README.md) | a workbook in, `.xlsx` / `.csv` out — build, patch the original, `download()` |
121
- | [`create-sheetpane`](packages/create-sheetpane/README.md) | `npm create sheetpane` — a working app to start from |
122
-
123
- **Plugins:**
124
-
125
- | package | adds |
126
- |---|---|
127
- | [`@sheetpane/numfmt`](packages/numfmt/README.md) | every Excel number format, with locales |
128
- | [`@sheetpane/formulas`](packages/formulas/README.md) | recalculation as you edit (IronCalc, WebAssembly, lazy-loaded) |
129
- | [`@sheetpane/styles`](packages/styles/README.md) | fills, bold, borders, alignment, merges |
130
- | [`@sheetpane/condfmt`](packages/condfmt/README.md) | conditional formatting, colour scales, data bars |
131
- | [`@sheetpane/validation`](packages/validation/README.md) | the workbook's own data-validation rules |
132
- | [`@sheetpane/images`](packages/images/README.md) | pictures anchored in the sheet |
133
- | [`@sheetpane/charts`](packages/charts/README.md) | bar, column, line, pie, doughnut and scatter charts |
134
- | [`@sheetpane/comments`](packages/comments/README.md) | notes and threaded comments, read and written |
135
- | [`@sheetpane/visibility`](packages/visibility/README.md) | hide, unhide and group rows, columns and sheets |
136
-
137
- **For hosts and their tools:**
138
-
139
- | package | what it is |
140
- |---|---|
141
211
  | [`@sheetpane/testing`](packages/testing/README.md) | locators and actions for Playwright and Vitest browser mode |
142
212
  | [`@sheetpane/fixtures`](packages/fixtures/README.md) | known workbooks for your tests |
143
213
  | [`@sheetpane/skills`](packages/skills/README.md) | agent skills for Claude Code, Kiro and Codex |
144
- | [`@sheetpane/mcp`](packages/mcp/README.md) | a read-only MCP server describing the API and inspecting workbooks |
214
+ | [`@sheetpane/mcp`](packages/mcp/README.md) | a read-only MCP server: the API, and workbook inspection |
145
215
 
146
- **Dependencies:** none at runtime outside the project, except the two engines
147
- two plugins use — [`numfmt`](https://github.com/borgar/numfmt) and
216
+ **Dependencies:** none at runtime outside the project, except the engines two
217
+ plugins use — [`numfmt`](https://github.com/borgar/numfmt) and
148
218
  [IronCalc](https://github.com/ironcalc/IronCalc). Every dependency is free and
149
219
  open source ([law L2](records/0004_LAW-2-foss-dependencies.md)).
150
220
 
151
221
  ---
152
222
 
153
- ## Develop
223
+ ## Contributing
224
+
225
+ **Welcome — especially failing files and bug reports.** You need **Node 22+**,
226
+ **pnpm 10** and [**just**](https://just.systems).
227
+
228
+ ```sh
229
+ just install # pnpm install --frozen-lockfile
230
+ just check # type-check, test (Node + Chromium) and build
231
+ just demo-html # the plain HTML demo at http://127.0.0.1:5173
232
+ ```
233
+
234
+ <details>
235
+ <summary><b>Every command</b></summary>
154
236
 
155
- Requires **Node 20+**, **pnpm 10** and [**just**](https://just.systems).
237
+ <br>
156
238
 
157
239
  | command | does |
158
240
  |---|---|
159
- | `just install` | `pnpm install --frozen-lockfile` |
160
- | `just check` | type-check, test (Node + Chromium, with coverage) and build |
161
241
  | `just test-pkg <name>` | one package's tests, e.g. `just test-pkg grid` |
162
242
  | `just test-browsers` | the browser suites in Chromium, Firefox and WebKit |
163
- | `just demo-html` | the plain HTML demo at http://127.0.0.1:5173 |
164
243
  | `just demo-react` | the React 19 demo at http://127.0.0.1:5174 |
165
244
  | `just demo-check` | both demos driven end to end in Chromium |
166
245
  | `just pack-check` | every package packed as npm would publish it, and checked |
167
- | `just release <version>` | bump every package together, check, tag; CI publishes |
246
+ | `just release <version>` | bump every package together, check, push; a GitHub Release publishes ([RELEASING.md](RELEASING.md)) |
247
+
248
+ </details>
168
249
 
169
250
  **How the project is run.** Every rule lives in one record under
170
251
  [`records/`](records/README.md); the live queue is
171
252
  [`work/OPEN-WORK.md`](work/OPEN-WORK.md); the map is
172
- [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md). Much of the code was written
173
- with AI coding agents working under those records — the tests, not the prose,
174
- are the evidence. Start at [`CLAUDE.md`](CLAUDE.md) if you are one.
253
+ [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md). Much of the code was written by
254
+ AI coding agents working under those records — **the tests, not the prose, are
255
+ the evidence**. Start at [`CLAUDE.md`](CLAUDE.md) if you are one.
175
256
 
176
- **Contributions are welcome**, especially failing files and bug reports.
257
+ ---
177
258
 
178
- ## Licence
259
+ <div align="center">
179
260
 
180
261
  [MIT](LICENSE) © Arpit Arya
181
262
 
263
+ </div>
264
+
182
265
 
183
266
  ---
184
267
 
185
- <!-- source: packages/grid/README.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/packages/grid/README.md) -->
268
+ <!-- source: packages/grid/README.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/packages/grid/README.md) -->
186
269
 
187
270
  # @sheetpane/grid
188
271
 
@@ -202,9 +285,7 @@ or outside; no font of its own — yours is used.
202
285
  pnpm add @sheetpane/grid # or npm / yarn
203
286
  ```
204
287
 
205
- Starting from nothing? `npm create sheetpane@latest` makes a working app —
206
- vanilla, React, Next.js, Vue, Angular or Svelte — with both elements, a sample
207
- file, a rules file and one test. Try it first in the [live playground](https://arpitarya.github.io/sheetpane/) or
288
+ Try it first in the [live playground](https://arpitarya.github.io/sheetpane/) or
208
289
  open a [starter in StackBlitz](https://github.com/arpitarya/sheetpane/blob/main/examples/starters/README.md)
209
290
  (from the first published release).
210
291
 
@@ -620,12 +701,12 @@ The specs are the records [SR-GRID](../../records/0105_GRID.md) and
620
701
 
621
702
  ---
622
703
 
623
- <!-- source: packages/export/README.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/packages/export/README.md) -->
704
+ <!-- source: packages/export/README.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/packages/export/README.md) -->
624
705
 
625
706
  # @sheetpane/export
626
707
 
627
708
  **A plain JavaScript object in, an `.xlsx` or `.csv` file out.** No runtime
628
- dependencies. Runs in evergreen browsers and Node 20+. Used by
709
+ dependencies. Runs in evergreen browsers and Node 22+. Used by
629
710
  `@sheetpane/grid`'s Export button; usable on its own.
630
711
 
631
712
  ```sh
@@ -767,7 +848,7 @@ The spec is the record [SR-EXPORT](../../records/0103_EXPORT.md).
767
848
 
768
849
  ---
769
850
 
770
- <!-- source: packages/import/README.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/packages/import/README.md) -->
851
+ <!-- source: packages/import/README.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/packages/import/README.md) -->
771
852
 
772
853
  # @sheetpane/import
773
854
 
@@ -829,7 +910,7 @@ The spec is the record [SR-IMPORT](../../records/0104_IMPORT.md).
829
910
 
830
911
  ---
831
912
 
832
- <!-- source: packages/numfmt/README.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/packages/numfmt/README.md) -->
913
+ <!-- source: packages/numfmt/README.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/packages/numfmt/README.md) -->
833
914
 
834
915
  # @sheetpane/numfmt
835
916
 
@@ -940,7 +1021,7 @@ How plugins plug in: [`work/compare/plugins.compare.md`](../../work/compare/plug
940
1021
 
941
1022
  ---
942
1023
 
943
- <!-- source: packages/formulas/README.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/packages/formulas/README.md) -->
1024
+ <!-- source: packages/formulas/README.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/packages/formulas/README.md) -->
944
1025
 
945
1026
  # @sheetpane/formulas
946
1027
 
@@ -1053,7 +1134,7 @@ How plugins plug in: [`work/compare/plugins.compare.md`](../../work/compare/plug
1053
1134
 
1054
1135
  ---
1055
1136
 
1056
- <!-- source: packages/styles/README.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/packages/styles/README.md) -->
1137
+ <!-- source: packages/styles/README.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/packages/styles/README.md) -->
1057
1138
 
1058
1139
  # @sheetpane/styles
1059
1140
 
@@ -1153,7 +1234,7 @@ How plugins plug in: [`work/compare/plugins.compare.md`](../../work/compare/plug
1153
1234
 
1154
1235
  ---
1155
1236
 
1156
- <!-- source: packages/validation/README.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/packages/validation/README.md) -->
1237
+ <!-- source: packages/validation/README.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/packages/validation/README.md) -->
1157
1238
 
1158
1239
  # @sheetpane/validation
1159
1240
 
@@ -1309,7 +1390,7 @@ the rule rows when you need them.
1309
1390
 
1310
1391
  ---
1311
1392
 
1312
- <!-- source: packages/condfmt/README.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/packages/condfmt/README.md) -->
1393
+ <!-- source: packages/condfmt/README.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/packages/condfmt/README.md) -->
1313
1394
 
1314
1395
  # @sheetpane/condfmt
1315
1396
 
@@ -1427,7 +1508,7 @@ How plugins plug in: [`work/compare/plugins.compare.md`](../../work/compare/plug
1427
1508
 
1428
1509
  ---
1429
1510
 
1430
- <!-- source: packages/images/README.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/packages/images/README.md) -->
1511
+ <!-- source: packages/images/README.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/packages/images/README.md) -->
1431
1512
 
1432
1513
  # @sheetpane/images
1433
1514
 
@@ -1513,7 +1594,7 @@ LibreOffice are owed by W-30.
1513
1594
 
1514
1595
  ---
1515
1596
 
1516
- <!-- source: packages/charts/README.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/packages/charts/README.md) -->
1597
+ <!-- source: packages/charts/README.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/packages/charts/README.md) -->
1517
1598
 
1518
1599
  # @sheetpane/charts
1519
1600
 
@@ -1647,7 +1728,7 @@ How plugins plug in: [`work/compare/plugins.compare.md`](../../work/compare/plug
1647
1728
 
1648
1729
  ---
1649
1730
 
1650
- <!-- source: packages/comments/README.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/packages/comments/README.md) -->
1731
+ <!-- source: packages/comments/README.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/packages/comments/README.md) -->
1651
1732
 
1652
1733
  # @sheetpane/comments
1653
1734
 
@@ -1783,7 +1864,7 @@ Spec: [`work/open/W-22-comments.md`](../../work/open/W-22-comments.md).
1783
1864
 
1784
1865
  ---
1785
1866
 
1786
- <!-- source: packages/visibility/README.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/packages/visibility/README.md) -->
1867
+ <!-- source: packages/visibility/README.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/packages/visibility/README.md) -->
1787
1868
 
1788
1869
  # @sheetpane/visibility
1789
1870
 
@@ -1821,7 +1902,7 @@ Spec: [SR-VISIBILITY](../../records/0121_VISIBILITY.md). MIT.
1821
1902
 
1822
1903
  ---
1823
1904
 
1824
- <!-- source: packages/fixtures/README.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/packages/fixtures/README.md) -->
1905
+ <!-- source: packages/fixtures/README.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/packages/fixtures/README.md) -->
1825
1906
 
1826
1907
  # @sheetpane/fixtures
1827
1908
 
@@ -1859,7 +1940,7 @@ The spec is the record [SR-FIXTURES](../../records/0115_FIXTURES.md).
1859
1940
 
1860
1941
  ---
1861
1942
 
1862
- <!-- source: packages/testing/README.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/packages/testing/README.md) -->
1943
+ <!-- source: packages/testing/README.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/packages/testing/README.md) -->
1863
1944
 
1864
1945
  # @sheetpane/testing
1865
1946
 
@@ -1909,7 +1990,7 @@ The spec is the record [SR-TESTING](../../records/0118_TESTING.md).
1909
1990
 
1910
1991
  ---
1911
1992
 
1912
- <!-- source: packages/skills/README.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/packages/skills/README.md) -->
1993
+ <!-- source: packages/skills/README.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/packages/skills/README.md) -->
1913
1994
 
1914
1995
  # @sheetpane/skills
1915
1996
 
@@ -1980,7 +2061,7 @@ the elements do not have. The spec is the record
1980
2061
 
1981
2062
  ---
1982
2063
 
1983
- <!-- source: packages/mcp/README.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/packages/mcp/README.md) -->
2064
+ <!-- source: packages/mcp/README.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/packages/mcp/README.md) -->
1984
2065
 
1985
2066
  # @sheetpane/mcp
1986
2067
 
@@ -2020,60 +2101,7 @@ Spec: [SR-MCP](../../records/0119_MCP.md). MIT.
2020
2101
 
2021
2102
  ---
2022
2103
 
2023
- <!-- source: packages/create-sheetpane/README.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/packages/create-sheetpane/README.md) -->
2024
-
2025
- # create-sheetpane
2026
-
2027
- **A working sheetpane app in one command.**
2028
-
2029
- ```sh
2030
- npm create sheetpane@latest # asks: folder, template, skills, MCP server
2031
- npm create sheetpane@latest my-app -- --template react --skills host --yes
2032
- pnpm create sheetpane my-app --template vue
2033
- ```
2034
-
2035
- | template | what you get |
2036
- |---|---|
2037
- | `vanilla` | Vite + TypeScript, no framework |
2038
- | `react` | React 19 on Vite; `onsheetpane-…` props typed by `@sheetpane/grid/jsx` |
2039
- | `next` | Next.js App Router; the sheet loads on the client only (`next/dynamic`, `ssr: false`) |
2040
- | `vue` | Vue 3 on Vite; `isCustomElement` set, typed by `@sheetpane/grid/vue` |
2041
- | `angular` | Angular, a standalone component with `CUSTOM_ELEMENTS_SCHEMA` (needs Node ^22.22.3 or ^24.15.0) |
2042
- | `svelte` | Svelte 5 on Vite; typed by `@sheetpane/grid/svelte` |
2043
-
2044
- Every template has the same app: `<sheetpane-excel>` and `<sheetpane-errors>`
2045
- on one `pane`, side by side, and the sheet again behind an **Open in a pop-up**
2046
- button (`display="popup"`); the showcase workbook and its rules file from
2047
- `@sheetpane/fixtures`; one Playwright test on `@sheetpane/testing` (the file
2048
- loads, the rules flag a cell, an edit reaches the download); and a README.
2049
- Framework versions are pinned exactly — the ones CI built and tested — and
2050
- every `@sheetpane/*` package at this package's version (they are released
2051
- together).
2052
-
2053
- | option | does |
2054
- |---|---|
2055
- | `--template <t>` | one of the six above |
2056
- | `--skills all \| host \| none` | runs `sheetpane-skills install --all` after installing — `host` leaves out the two skills for contributing to sheetpane itself; `none` skips it |
2057
- | `--mcp`, `--no-mcp` | register the sheetpane MCP server (`@sheetpane/mcp`) with Claude Code, Kiro and Codex, or not |
2058
- | `--pm npm \| pnpm \| yarn` | the package manager (default: the one that ran `create`) |
2059
- | `--no-install` | copy the files only |
2060
- | `-y`, `--yes` | no questions; defaults: `sheetpane-app`, `vanilla`, `host`, MCP on |
2061
-
2062
- The folder must be new or empty; nothing outside it is written. No
2063
- dependencies, no `postinstall`.
2064
-
2065
- Every template is also in the repository, scaffolded by this CLI, with an
2066
- **Open in StackBlitz** link: [`examples/starters`](https://github.com/arpitarya/sheetpane/blob/main/examples/starters/README.md)
2067
- (they install from npm, so they run once the version is published).
2068
-
2069
- The spec is the record SR-CREATE; the repo checks every template on each push
2070
- with `just create-check` (scaffold, install from the packed tarballs, build,
2071
- test).
2072
-
2073
-
2074
- ---
2075
-
2076
- <!-- source: docs/ARCHITECTURE.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/docs/ARCHITECTURE.md) -->
2104
+ <!-- source: docs/ARCHITECTURE.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/docs/ARCHITECTURE.md) -->
2077
2105
 
2078
2106
  # sheetpane — architecture
2079
2107
 
@@ -2158,7 +2186,7 @@ flowchart TB
2158
2186
  | **`<sheetpane-errors>`** | the **error** component: the list (filter, resolve, delete) and the New error form; always on the same workbook as the sheet | `packages/grid/src/errors/` (W-02) | [contract verdict](../work/compare/workbook-and-error-contract.compare.md) |
2159
2187
  | **`@sheetpane/export`** | a plain object or the original file plus changes → `.xlsx` or `.csv` bytes; `download()` to save it (W-12, [SR-DOWNLOAD](../records/0101_DOWNLOAD.md)); ZIP read and write, CRC-32, XML, CSV; zero dependencies; also usable without the UI | `packages/export/` (W-03, W-12) | [export verdict](../work/compare/export-shape.compare.md) |
2160
2188
  | **plugins** | eight separate packages that register against the one workbook through six hooks (`format`, `decorate`, `derive`, `validate`, `overlay`, `exportPatch`) and add controls to six slots the grid draws; the grid with none installed is unchanged | `packages/numfmt/` `formulas/` `styles/` `validation/` `condfmt/` `images/` `charts/` `comments/` (W-15–W-22); the host in `packages/grid/src/plugins/` (W-14) | [plugins verdict](../work/compare/plugins.compare.md) |
2161
- | **browser APIs** | `DecompressionStream` / `CompressionStream` (`deflate-raw`), `DOMParser`, `customElements`, `Blob` | the browser; Node 20+ uses `zlib` for export | the reading and export verdicts |
2189
+ | **browser APIs** | `DecompressionStream` / `CompressionStream` (`deflate-raw`), `DOMParser`, `customElements`, `Blob` | the browser; Node 22+ uses `zlib` for export | the reading and export verdicts |
2162
2190
 
2163
2191
  **Dependency direction:** host → plugins → `@sheetpane/grid` → `@sheetpane/import` → `@sheetpane/export` → browser.
2164
2192
  Nothing points the other way. `@sheetpane/export` has no runtime dependencies;
@@ -2305,7 +2333,7 @@ How it is all tested — Vitest, real browsers, fixtures, goldens — is the
2305
2333
 
2306
2334
  ---
2307
2335
 
2308
- <!-- source: docs/GLOSSARY.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/docs/GLOSSARY.md) -->
2336
+ <!-- source: docs/GLOSSARY.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/docs/GLOSSARY.md) -->
2309
2337
 
2310
2338
  # Glossary
2311
2339
 
@@ -2342,7 +2370,7 @@ How it is all tested — Vitest, real browsers, fixtures, goldens — is the
2342
2370
 
2343
2371
  ---
2344
2372
 
2345
- <!-- source: docs/ERRORS.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/docs/ERRORS.md) -->
2373
+ <!-- source: docs/ERRORS.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/docs/ERRORS.md) -->
2346
2374
 
2347
2375
  # Error codes
2348
2376
 
@@ -3154,7 +3182,7 @@ throws: each problem is one `note` error, `author: 'host'`, with its `code`.
3154
3182
 
3155
3183
  ---
3156
3184
 
3157
- <!-- source: records/0001_LAWS.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0001_LAWS.md) -->
3185
+ <!-- source: records/0001_LAWS.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0001_LAWS.md) -->
3158
3186
 
3159
3187
  # SR-LAWS — the laws have exactly one home each
3160
3188
 
@@ -3239,7 +3267,7 @@ carries a law's normative text.
3239
3267
 
3240
3268
  ---
3241
3269
 
3242
- <!-- source: records/0002_LAW-0-authority.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0002_LAW-0-authority.md) -->
3270
+ <!-- source: records/0002_LAW-0-authority.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0002_LAW-0-authority.md) -->
3243
3271
 
3244
3272
  # SR-LAW-0 — L0, the records are the only source of truth
3245
3273
 
@@ -3355,7 +3383,7 @@ is never listed here.*
3355
3383
 
3356
3384
  ---
3357
3385
 
3358
- <!-- source: records/0003_LAW-1-retired-records-archived.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0003_LAW-1-retired-records-archived.md) -->
3386
+ <!-- source: records/0003_LAW-1-retired-records-archived.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0003_LAW-1-retired-records-archived.md) -->
3359
3387
 
3360
3388
  # SR-LAW-1 — L1, a retired record is archived, never deleted
3361
3389
 
@@ -3448,7 +3476,7 @@ is never listed here.*
3448
3476
 
3449
3477
  ---
3450
3478
 
3451
- <!-- source: records/0004_LAW-2-foss-dependencies.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0004_LAW-2-foss-dependencies.md) -->
3479
+ <!-- source: records/0004_LAW-2-foss-dependencies.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0004_LAW-2-foss-dependencies.md) -->
3452
3480
 
3453
3481
  # SR-LAW-2 — L2, every dependency is free and open source
3454
3482
 
@@ -3557,7 +3585,7 @@ is never listed here.*
3557
3585
 
3558
3586
  ---
3559
3587
 
3560
- <!-- source: records/0005_LAW-3-xlsx-and-csv-only.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0005_LAW-3-xlsx-and-csv-only.md) -->
3588
+ <!-- source: records/0005_LAW-3-xlsx-and-csv-only.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0005_LAW-3-xlsx-and-csv-only.md) -->
3561
3589
 
3562
3590
  # SR-LAW-3 — L3, sheetpane supports XLSX and CSV only
3563
3591
 
@@ -3670,7 +3698,7 @@ is never listed here.*
3670
3698
 
3671
3699
  ---
3672
3700
 
3673
- <!-- source: records/0006_LAW-4-web-component.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0006_LAW-4-web-component.md) -->
3701
+ <!-- source: records/0006_LAW-4-web-component.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0006_LAW-4-web-component.md) -->
3674
3702
 
3675
3703
  # SR-LAW-4 — L4, sheetpane is always a web component
3676
3704
 
@@ -3790,7 +3818,7 @@ is never listed here.*
3790
3818
 
3791
3819
  ---
3792
3820
 
3793
- <!-- source: records/0007_LAW-5-host-font.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0007_LAW-5-host-font.md) -->
3821
+ <!-- source: records/0007_LAW-5-host-font.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0007_LAW-5-host-font.md) -->
3794
3822
 
3795
3823
  # SR-LAW-5 — L5, the font is the browser's default unless the host sets one
3796
3824
 
@@ -3909,7 +3937,7 @@ is never listed here.*
3909
3937
 
3910
3938
  ---
3911
3939
 
3912
- <!-- source: records/0008_LAW-6-edits-in-memory-export-with-changes.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0008_LAW-6-edits-in-memory-export-with-changes.md) -->
3940
+ <!-- source: records/0008_LAW-6-edits-in-memory-export-with-changes.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0008_LAW-6-edits-in-memory-export-with-changes.md) -->
3913
3941
 
3914
3942
  # SR-LAW-6 — L6, every change is held in memory and export carries it
3915
3943
 
@@ -4020,7 +4048,7 @@ is never listed here.*
4020
4048
 
4021
4049
  ---
4022
4050
 
4023
- <!-- source: records/0009_LAW-7-surfaces-always-in-sync.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0009_LAW-7-surfaces-always-in-sync.md) -->
4051
+ <!-- source: records/0009_LAW-7-surfaces-always-in-sync.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0009_LAW-7-surfaces-always-in-sync.md) -->
4024
4052
 
4025
4053
  # SR-LAW-7 — L7, the view, edit and error components are always in sync
4026
4054
 
@@ -4129,7 +4157,7 @@ is never listed here.*
4129
4157
 
4130
4158
  ---
4131
4159
 
4132
- <!-- source: records/0010_LAW-8-npm-package.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0010_LAW-8-npm-package.md) -->
4160
+ <!-- source: records/0010_LAW-8-npm-package.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0010_LAW-8-npm-package.md) -->
4133
4161
 
4134
4162
  # SR-LAW-8 — L8, sheetpane is consumed as an npm package
4135
4163
 
@@ -4190,7 +4218,7 @@ sheetpane is a library other applications build on. The decider ruled on
4190
4218
  importing either package in Node without a browser must be safe.
4191
4219
  `@sheetpane/grid` registers its elements only when `customElements` exists;
4192
4220
  `@sheetpane/export` touches the DOM only inside `download()` (which throws a
4193
- clear error without a `document`) and otherwise runs in Node 20+.
4221
+ clear error without a `document`) and otherwise runs in Node 22+.
4194
4222
  - **Publishing (ruled 2026-10-18):** the two packages are versioned in
4195
4223
  lockstep — one version number, released together — so `@sheetpane/grid@x.y.z`
4196
4224
  always pins `@sheetpane/export@x.y.z`. The root `package.json` is the
@@ -4238,7 +4266,7 @@ is never listed here.*
4238
4266
 
4239
4267
  ---
4240
4268
 
4241
- <!-- source: records/0011_LAW-9-memory-is-a-javascript-object.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0011_LAW-9-memory-is-a-javascript-object.md) -->
4269
+ <!-- source: records/0011_LAW-9-memory-is-a-javascript-object.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0011_LAW-9-memory-is-a-javascript-object.md) -->
4242
4270
 
4243
4271
  # SR-LAW-9 — L9, memory means a JavaScript object, for now
4244
4272
 
@@ -4336,7 +4364,7 @@ is never listed here.*
4336
4364
 
4337
4365
  ---
4338
4366
 
4339
- <!-- source: records/0101_DOWNLOAD.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0101_DOWNLOAD.md) -->
4367
+ <!-- source: records/0101_DOWNLOAD.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0101_DOWNLOAD.md) -->
4340
4368
 
4341
4369
  # SR-DOWNLOAD — download(): export output in, a saved .xlsx or .csv out
4342
4370
 
@@ -4582,7 +4610,7 @@ is never listed here.*
4582
4610
 
4583
4611
  ---
4584
4612
 
4585
- <!-- source: records/0102_BUILD.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0102_BUILD.md) -->
4613
+ <!-- source: records/0102_BUILD.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0102_BUILD.md) -->
4586
4614
 
4587
4615
  # SR-BUILD — the monorepo build
4588
4616
 
@@ -4672,8 +4700,14 @@ pnpm and a justfile; the testing verdict chose the runner.
4672
4700
  catch a real download through Playwright. Dev-only readers
4673
4701
  for read-back tests (ExcelJS, PapaParse, fflate) are dev dependencies of
4674
4702
  the package that uses them; so is any `@sheetpane/*` package a test
4675
- imports (`workspace:*`). Coverage (v8) is reported on every run; 90 % lines on
4676
- `packages/export/src/**` fails the run. A wall-clock budget (a frame gap,
4703
+ imports (`workspace:*`). Coverage (v8) is reported on every `just test`;
4704
+ 90 % lines on `packages/export/src/**` fails the run — in CI, the
4705
+ `coverage` job runs `@sheetpane/export`'s tests with coverage and holds that
4706
+ line, while the sharded test jobs collect none. CI shards (`--shard=k/n`)
4707
+ are balanced by measured time, not Vitest's path hash: `vitest.config.ts`'s
4708
+ sequencer puts the longest file first, each into the lightest shard, from
4709
+ `test/durations.json` (`just test-durations` re-measures it; an unlisted
4710
+ file counts 1 s). A wall-clock budget (a frame gap,
4677
4711
  pixels scrolled in 5 s) is asserted on a developer machine and only
4678
4712
  reported on a shared CI runner: `vitest.config.ts` provides
4679
4713
  `timingBudgets` (off when `CI` is set), the grid's test helpers export it
@@ -4710,17 +4744,33 @@ pnpm and a justfile; the testing verdict chose the runner.
4710
4744
  follows `export … from`, `export *`, aliases, re-exported imports and
4711
4745
  `@sheetpane/<pkg>` re-exports itself — and fails naming each symbol; it
4712
4746
  also type-checks every `@example` code block against the built packages.
4713
- 8. **CI:** `.github/workflows/ci.yml` runs install, `just check` and
4714
- `just pack-check` on every push to `main` and every PR (Chromium), and a
4715
- `create` job — a matrix over the six `create-sheetpane` templates running
4716
- `just create-check <template>` (SR-CREATE decision 5);
4747
+ 8. **CI:** `.github/workflows/ci.yml` runs on every push to any branch, every
4748
+ PR and on demand, every job in parallel and set up by
4749
+ `.github/actions/setup` (Node; the pinned pnpm, installed with npm; the
4750
+ pnpm store cached by the lockfile; Playwright's Chromium cached by its
4751
+ version, without apt dependencies — the Ubuntu image carries them). Jobs:
4752
+ `lint` (version parity, build, typecheck — the repo's lint); `test` (the
4753
+ whole suite, Node + Chromium, in four balanced shards, decision 6);
4754
+ `coverage`; `package` (`pack-check` and `demo-check` in Chromium);
4755
+ `portable` (ubuntu, macos and windows × Node 22 and the current LTS:
4756
+ version parity, build, typecheck, the Node unit tests, and
4757
+ `scripts/smoke-built.mjs`, which installs every package from its packed
4758
+ tarball with npm into an empty folder and imports it from plain Node, so
4759
+ the BUILT package is what is tested); and `create` — a matrix over the six
4760
+ `create-sheetpane` templates running `scripts/create-check.mjs`
4761
+ (SR-CREATE decision 5). A branch's newer push cancels its older run; on
4762
+ `main` every run finishes, since `publish.yml` reads each commit's verdict.
4763
+ The target is about two minutes of wall time. Every package's `engines.node` is `>=22` (the
4764
+ decider, 2026-10-19; Node 20 is past end of life). Build scripts resolve
4765
+ paths with `fileURLToPath`, never `URL.pathname`, so they run on Windows;
4717
4766
  `nightly.yml` runs the browser suites in Chromium, Firefox and WebKit;
4718
4767
  `pages.yml` (W-44) on a release tag builds the live playground — `just
4719
4768
  site` → `site/` (`scripts/build-site.mjs`: the HTML gallery at `/` with
4720
4769
  every file its import map names copied in, the React gallery at `/react/`
4721
4770
  with a relative base, the version in both headers, no third-party script)
4722
4771
  — checks it with `demo-check`, and deploys it to GitHub Pages;
4723
- `release.yml` on the same tag publishes the packages (decision 9).
4772
+ `publish.yml` publishes the packages when a GitHub Release is published
4773
+ (decision 9).
4724
4774
  8b. **Starters and links (W-44):** `scripts/build-starters.mjs` (`just
4725
4775
  starters`) scaffolds every `create-sheetpane` template with the CLI itself
4726
4776
  into `examples/starters/<template>` (not workspace members) with an index
@@ -4732,17 +4782,32 @@ pnpm and a justfile; the testing verdict chose the runner.
4732
4782
  `create-sheetpane` READMEs, `llms.txt` and the setup skill link the live
4733
4783
  playground (`https://arpitarya.github.io/sheetpane/`, decision 8) and the
4734
4784
  starters.
4735
- 9. **Release:** `just release <version>` — clean tree required; every package
4736
- set to the version (`scripts/release.mjs`, which then rebuilds the
4737
- starters, the Claude Code plugin and `llms.txt`); `just check pack-check`; a
4738
- commit and an annotated tag `v<version>`, both pushed. Nothing is published
4739
- from a laptop: the tag runs `.github/workflows/release.yml`, which fails
4740
- unless every package carries the tag's version, runs `just check`,
4741
- `pack-check` and `demo-check`, then `pnpm -r publish --access public` with
4742
- npm provenance (the `NPM_TOKEN` repository secret) and opens a GitHub
4743
- release with every package's tarball attached. `pnpm -r publish` skips a
4744
- version already on npm, so a failed run is re-run, not re-tagged. Never
4745
- `npm publish`: npm ships `workspace:*` literally.
4785
+ 9. **Release** (`RELEASING.md` is the walkthrough): `just release <version>`
4786
+ — clean tree required; every package set to the version
4787
+ (`scripts/release.mjs`, which then rebuilds the starters, the Claude Code
4788
+ plugin and `llms.txt`); version parity; `just check pack-check`; a commit
4789
+ pushed to `main` as `chore(release): v<version>` (commits follow
4790
+ Conventional Commits, the decider, 2026-10-19), no tag; it refuses unless
4791
+ `CHANGELOG.md` has a `## <version>` section. After `ci.yml` is green on
4792
+ that commit, `just publish <version>` publishes the GitHub Release
4793
+ `v<version> — <summary>` (the heading's summary) with that section as its
4794
+ notes (`scripts/release-notes.mjs`),
4795
+ and that alone runs
4796
+ `.github/workflows/publish.yml`. Its `build` job refuses unless `ci.yml`
4797
+ concluded `success` on the commit on `main`, checks version parity and that
4798
+ the tag is the packages' version, builds once and packs every public
4799
+ package (`pnpm pack` rewrites `workspace:*`) into one artifact. Its
4800
+ `publish-npm` job (environment `npm`) never rebuilds: it checks each
4801
+ tarball (README, LICENSE and the build output present; no `src/`, `test/`
4802
+ or `workspace:`) and publishes it with npm ≥ 11.5.1, `--provenance`, the
4803
+ `next` dist-tag for a version with a `-` and `latest` otherwise; a version
4804
+ already on npm is skipped, so a failed run is re-run. **No token** — npm
4805
+ trusted publishing (OIDC; the decider's ruling, 2026-10-19: no
4806
+ `NPM_TOKEN`). Trusted publishing needs the package to exist: the first
4807
+ version is published once by hand, then each package trusts `publish.yml`
4808
+ and environment `npm` (npmjs.com, or `scripts/npm-trust.sh`). Neither name
4809
+ may change: npmjs.com is bound to both. Never `npm publish` from a package
4810
+ folder: npm ships `workspace:*` literally.
4746
4811
  9a. **llms.txt (W-38):** `scripts/build-llms.mjs` (`just llms`, no
4747
4812
  dependencies, deterministic) writes `llms.txt` (llmstxt.org: an H1, a
4748
4813
  summary, sections Start · Elements · Plugins · Rules and errors · Testing
@@ -4754,6 +4819,12 @@ pnpm and a justfile; the testing verdict chose the runner.
4754
4819
  release); `release.mjs` rewrites them with the version, and
4755
4820
  `test/llms.test.ts` fails when either is stale (it prints the full file's
4756
4821
  size — over 200 KB is a hint to split per package).
4822
+ 9b. **Version parity:** `scripts/check-version-parity.mjs` (`just
4823
+ version-parity`; Node standard library only) fails when any file holding
4824
+ the version disagrees: every `packages/*/package.json`, the plugin's
4825
+ `plugin.json`, each starter's `@sheetpane/*` pins and `tree/v…` links, and
4826
+ `blob/v…` in `llms.txt` and `llms-full.txt`. It runs in `ci.yml` (every
4827
+ job) and in `publish.yml`.
4757
4828
  10. **Pack check:** `scripts/pack-check.mjs` packs each package as publish
4758
4829
  would and fails on a surviving `workspace:` or `source` entry or a missing
4759
4830
  exported file (for a subpath pattern such as `./files/*`, its folder).
@@ -4792,9 +4863,10 @@ pnpm and a justfile; the testing verdict chose the runner.
4792
4863
  - **Codes (W-40):** `test/error-codes.test.ts` and its append-only `test/error-codes.lock.json` are SR-CODES' (carved out of `test/`); `test/ssr.test.ts` asserts the SSR refusal's code.
4793
4864
  - **Enforced today:** decisions 6, 7 and 10 by `just check` / `just
4794
4865
  pack-check`, locally and in CI.
4795
- - **Owed by the decider:** the `NPM_TOKEN` repository secret (an npm granular
4796
- token with publish rights on `@sheetpane/*` and `create-sheetpane`), before
4797
- `release.yml` can publish; the `@sheetpane` organisation exists.
4866
+ - **Owed by the decider:** the first publish by hand (any package not yet on
4867
+ npm), a trusted publisher per package (`publish.yml`, environment `npm`,
4868
+ *Allow npm publish*) and the `npm` environment in GitHub — `RELEASING.md`
4869
+ §One time; the `@sheetpane` organisation exists.
4798
4870
  - **Still open:** the licence check over the whole lockfile (backlog B-008);
4799
4871
  decision 7 checks only the runtime allow-list.
4800
4872
  - **Plugins must keep `@sheetpane/grid` as a dev dependency too** (a peer is
@@ -4837,7 +4909,7 @@ is never listed here.*
4837
4909
  - [`package.json`](../package.json) · [`pnpm-workspace.yaml`](../pnpm-workspace.yaml) · [`justfile`](../justfile) · [`tsconfig.base.json`](../tsconfig.base.json)
4838
4910
  - [`vitest.config.ts`](../vitest.config.ts) · [`test/packages.test.ts`](../test/packages.test.ts)
4839
4911
  - [`scripts/pack-check.mjs`](../scripts/pack-check.mjs) · [`scripts/release.mjs`](../scripts/release.mjs) · [`scripts/build-grid-css.mjs`](../scripts/build-grid-css.mjs)
4840
- - [`.github/workflows/ci.yml`](../.github/workflows/ci.yml) · [`.github/workflows/nightly.yml`](../.github/workflows/nightly.yml) · [`.github/workflows/release.yml`](../.github/workflows/release.yml)
4912
+ - [`.github/workflows/ci.yml`](../.github/workflows/ci.yml) · [`.github/workflows/nightly.yml`](../.github/workflows/nightly.yml) · [`.github/workflows/publish.yml`](../.github/workflows/publish.yml) · [`RELEASING.md`](../RELEASING.md)
4841
4913
 
4842
4914
  **Project docs**
4843
4915
 
@@ -4846,7 +4918,7 @@ is never listed here.*
4846
4918
 
4847
4919
  ---
4848
4920
 
4849
- <!-- source: records/0103_EXPORT.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0103_EXPORT.md) -->
4921
+ <!-- source: records/0103_EXPORT.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0103_EXPORT.md) -->
4850
4922
 
4851
4923
  # SR-EXPORT — @sheetpane/export
4852
4924
 
@@ -5016,7 +5088,7 @@ else.
5016
5088
  ### Alternatives considered
5017
5089
 
5018
5090
  - Patch via `DOMParser` — rejected: Node has no DOM, and this package must
5019
- run in Node 20+.
5091
+ run in Node 22+.
5020
5092
  - Re-compressing untouched parts — rejected: copying the compressed bytes is
5021
5093
  faster and provably byte-identical.
5022
5094
  - A shared-string table in `toXlsx` — rejected by the verdict (inline strings
@@ -5055,7 +5127,7 @@ is never listed here.*
5055
5127
 
5056
5128
  ---
5057
5129
 
5058
- <!-- source: records/0104_IMPORT.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0104_IMPORT.md) -->
5130
+ <!-- source: records/0104_IMPORT.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0104_IMPORT.md) -->
5059
5131
 
5060
5132
  # SR-IMPORT — @sheetpane/import
5061
5133
 
@@ -5240,7 +5312,7 @@ is never listed here.*
5240
5312
 
5241
5313
  ---
5242
5314
 
5243
- <!-- source: records/0105_GRID.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0105_GRID.md) -->
5315
+ <!-- source: records/0105_GRID.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0105_GRID.md) -->
5244
5316
 
5245
5317
  # SR-GRID — @sheetpane/grid
5246
5318
 
@@ -5867,7 +5939,7 @@ is never listed here.*
5867
5939
 
5868
5940
  ---
5869
5941
 
5870
- <!-- source: records/0106_ERRORS.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0106_ERRORS.md) -->
5942
+ <!-- source: records/0106_ERRORS.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0106_ERRORS.md) -->
5871
5943
 
5872
5944
  # SR-ERRORS — <sheetpane-errors>
5873
5945
 
@@ -6008,7 +6080,7 @@ is never listed here.*
6008
6080
 
6009
6081
  ---
6010
6082
 
6011
- <!-- source: records/0107_IMAGES.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0107_IMAGES.md) -->
6083
+ <!-- source: records/0107_IMAGES.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0107_IMAGES.md) -->
6012
6084
 
6013
6085
  # SR-IMAGES — @sheetpane/images
6014
6086
 
@@ -6136,7 +6208,7 @@ is never listed here.*
6136
6208
 
6137
6209
  ---
6138
6210
 
6139
- <!-- source: records/0108_VALIDATION.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0108_VALIDATION.md) -->
6211
+ <!-- source: records/0108_VALIDATION.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0108_VALIDATION.md) -->
6140
6212
 
6141
6213
  # SR-VALIDATION — @sheetpane/validation
6142
6214
 
@@ -6270,7 +6342,7 @@ is never listed here.*
6270
6342
 
6271
6343
  ---
6272
6344
 
6273
- <!-- source: records/0109_NUMFMT.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0109_NUMFMT.md) -->
6345
+ <!-- source: records/0109_NUMFMT.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0109_NUMFMT.md) -->
6274
6346
 
6275
6347
  # SR-NUMFMT — @sheetpane/numfmt
6276
6348
 
@@ -6406,7 +6478,7 @@ is never listed here.*
6406
6478
 
6407
6479
  ---
6408
6480
 
6409
- <!-- source: records/0110_STYLES.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0110_STYLES.md) -->
6481
+ <!-- source: records/0110_STYLES.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0110_STYLES.md) -->
6410
6482
 
6411
6483
  # SR-STYLES — @sheetpane/styles
6412
6484
 
@@ -6552,7 +6624,7 @@ is never listed here.*
6552
6624
 
6553
6625
  ---
6554
6626
 
6555
- <!-- source: records/0111_FORMULAS.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0111_FORMULAS.md) -->
6627
+ <!-- source: records/0111_FORMULAS.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0111_FORMULAS.md) -->
6556
6628
 
6557
6629
  # SR-FORMULAS — @sheetpane/formulas
6558
6630
 
@@ -6716,7 +6788,7 @@ is never listed here.*
6716
6788
 
6717
6789
  ---
6718
6790
 
6719
- <!-- source: records/0112_COMMENTS.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0112_COMMENTS.md) -->
6791
+ <!-- source: records/0112_COMMENTS.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0112_COMMENTS.md) -->
6720
6792
 
6721
6793
  # SR-COMMENTS — @sheetpane/comments
6722
6794
 
@@ -6862,7 +6934,7 @@ is never listed here.*
6862
6934
 
6863
6935
  ---
6864
6936
 
6865
- <!-- source: records/0113_CHARTS.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0113_CHARTS.md) -->
6937
+ <!-- source: records/0113_CHARTS.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0113_CHARTS.md) -->
6866
6938
 
6867
6939
  # SR-CHARTS — @sheetpane/charts
6868
6940
 
@@ -7007,7 +7079,7 @@ is never listed here.*
7007
7079
 
7008
7080
  ---
7009
7081
 
7010
- <!-- source: records/0114_CONDFMT.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0114_CONDFMT.md) -->
7082
+ <!-- source: records/0114_CONDFMT.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0114_CONDFMT.md) -->
7011
7083
 
7012
7084
  # SR-CONDFMT — @sheetpane/condfmt
7013
7085
 
@@ -7159,7 +7231,7 @@ is never listed here.*
7159
7231
 
7160
7232
  ---
7161
7233
 
7162
- <!-- source: records/0115_FIXTURES.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0115_FIXTURES.md) -->
7234
+ <!-- source: records/0115_FIXTURES.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0115_FIXTURES.md) -->
7163
7235
 
7164
7236
  # SR-FIXTURES — @sheetpane/fixtures
7165
7237
 
@@ -7269,7 +7341,7 @@ is never listed here.*
7269
7341
 
7270
7342
  ---
7271
7343
 
7272
- <!-- source: records/0116_SKILLS.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0116_SKILLS.md) -->
7344
+ <!-- source: records/0116_SKILLS.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0116_SKILLS.md) -->
7273
7345
 
7274
7346
  # SR-SKILLS — @sheetpane/skills
7275
7347
 
@@ -7434,7 +7506,7 @@ is never listed here.*
7434
7506
 
7435
7507
  ---
7436
7508
 
7437
- <!-- source: records/0117_CODES.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0117_CODES.md) -->
7509
+ <!-- source: records/0117_CODES.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0117_CODES.md) -->
7438
7510
 
7439
7511
  # SR-CODES — stable error codes
7440
7512
 
@@ -7549,7 +7621,7 @@ is never listed here.*
7549
7621
 
7550
7622
  ---
7551
7623
 
7552
- <!-- source: records/0118_TESTING.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0118_TESTING.md) -->
7624
+ <!-- source: records/0118_TESTING.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0118_TESTING.md) -->
7553
7625
 
7554
7626
  # SR-TESTING — @sheetpane/testing
7555
7627
 
@@ -7651,7 +7723,7 @@ is never listed here.*
7651
7723
 
7652
7724
  ---
7653
7725
 
7654
- <!-- source: records/0119_MCP.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0119_MCP.md) -->
7726
+ <!-- source: records/0119_MCP.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0119_MCP.md) -->
7655
7727
 
7656
7728
  # SR-MCP — @sheetpane/mcp
7657
7729
 
@@ -7812,121 +7884,7 @@ is never listed here.*
7812
7884
 
7813
7885
  ---
7814
7886
 
7815
- <!-- source: records/0120_CREATE.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0120_CREATE.md) -->
7816
-
7817
- # SR-CREATE — create-sheetpane
7818
-
7819
- ## §1 — For humans
7820
-
7821
- **One command, a running app that is tested and agent-ready:** `npm create
7822
- sheetpane@latest` asks for a folder, a framework, the skills and the MCP
7823
- server, and writes an app that shows a sheet and its errors panel, loads a
7824
- sample workbook with a rules file, and has one test that passes.
7825
-
7826
- ```mermaid
7827
- flowchart LR
7828
- C["npm create sheetpane"] --> T["templates/_shared + templates/&lt;t&gt;"]
7829
- T --> I["install (npm · pnpm · yarn)"]
7830
- I --> S["sheetpane-skills install (+ MCP server)"]
7831
- S --> A["dev · build · preview · test"]
7832
- ```
7833
-
7834
- <details>
7835
- <summary><b>ASCII twin</b> — the same diagram, for terminals and diffs</summary>
7836
-
7837
- ```text
7838
- npm create sheetpane -> copy _shared + <template>, fill placeholders
7839
- -> install -> sheetpane-skills install [--skip contributor] [--no-mcp]
7840
- -> dev / build / preview / test (Playwright on @sheetpane/testing)
7841
- ```
7842
-
7843
- </details>
7844
-
7845
- ---
7846
-
7847
- ## §2 — For agents
7848
-
7849
- ### Decision
7850
-
7851
- 1. **The package** is unscoped `create-sheetpane` — `npm create <name>` runs
7852
- `create-<name>` — the one package outside `@sheetpane/` (SR-BUILD decision
7853
- 7's exception). It ships `bin/` and `templates/`, has no dependencies, no
7854
- build and no `postinstall`, and moves in lockstep with the rest.
7855
- 2. **The CLI** (`bin/create-sheetpane.mjs`, Node ≥ 20): a folder, `--template`
7856
- (`vanilla` · `react` · `next` · `vue` · `angular` · `svelte`), `--skills
7857
- all | host | none`, `--mcp` / `--no-mcp`, `--pm npm | pnpm | yarn` (default:
7858
- the one that ran it), `--no-install`, `--yes`. It asks only at a terminal.
7859
- It refuses a non-empty folder, writes nothing outside it, renames
7860
- `_gitignore`, fills `__SHEETPANE_<KEY>__` placeholders — the version from
7861
- its own `package.json` — then installs and runs `sheetpane-skills install
7862
- --all` (`--skip contributor` for `host`, `--no-mcp` when asked).
7863
- 3. **Every template is the same app:** `<sheetpane-excel>` (Edit) and
7864
- `<sheetpane-errors>` on one `pane`, side by side; the sheet again behind a
7865
- pop-up button (`display="popup"`); `showcase.xlsx` and its rules file
7866
- copied from `@sheetpane/fixtures` into `public/` by `scripts/sample.mjs`
7867
- before dev and build; one Playwright test on `@sheetpane/testing` (load, a
7868
- rules finding, an edit read back from the download); the W-37 typings
7869
- (`/jsx` for React and Next, `/vue`, `/svelte`); Angular through
7870
- `CUSTOM_ELEMENTS_SCHEMA`; Next.js client-only (`next/dynamic`, `ssr:
7871
- false`); a README pointing at the skills and the MCP server. No font
7872
- (SR-LAW-5).
7873
- 4. **Versions:** frameworks pinned exactly to what `create-check` built and
7874
- tested; TypeScript 6.0.3 in the templates (Angular 22 and svelte-check
7875
- need < 6.1). Every dependency is free and open source (SR-LAW-2).
7876
- 5. **The check** (`scripts/create-check.mjs`, `just create-check [templates]`,
7877
- and a CI job over the six): packs every package, runs the CLI from its own
7878
- tarball, points `@sheetpane/*` at the tarballs, installs, runs the skills
7879
- installer (and checks it wrote the skills, `.mcp.json` and `AGENTS.md`),
7880
- builds and runs the template's test. A template that cannot pass is
7881
- dropped, never shipped broken.
7882
-
7883
- ### Consequences
7884
-
7885
- - **Enforced today:** `test/create.test.ts` (27: every template scaffolded
7886
- with `--yes --no-install`, placeholders filled, the refusals, the install
7887
- and skills commands per package manager with a stubbed runner);
7888
- `create-check` per template in CI; `pack-check`.
7889
- - **Known:** Angular 22 needs Node ^22.22.3 or ^24.15.0 — the template's
7890
- `engines`, a CLI note and its README say so.
7891
- - **Owed:** StackBlitz links per template (W-44 DoD 2).
7892
-
7893
- ### Alternatives considered
7894
-
7895
- - `@sheetpane/create` (`npm create @sheetpane`) — kept as the fallback name;
7896
- the unscoped one is the shorter command.
7897
- - Downloading templates from the repository at run time — rejected: the
7898
- templates ship in the tarball, so the version matches and nothing is
7899
- fetched but the install.
7900
-
7901
- ### Reference (required)
7902
-
7903
- - [`packages/create-sheetpane/`](../packages/create-sheetpane/) · [`scripts/create-check.mjs`](../scripts/create-check.mjs)
7904
-
7905
- ### Veto condition
7906
-
7907
- **Reopen this decision if:** a template fails `create-check` on a framework
7908
- release and cannot be fixed by a version bump, or a host needs a template the
7909
- six do not cover.
7910
-
7911
- **How to check it:** `just create-check`
7912
-
7913
- ---
7914
-
7915
- ## References
7916
-
7917
- *Every source this record cites, gathered in one place. An archived document
7918
- is never listed here.*
7919
-
7920
- **Records** — [SR-BUILD](0102_BUILD.md) · [SR-SKILLS](0116_SKILLS.md) · [SR-MCP](0119_MCP.md) · [SR-TESTING](0118_TESTING.md) · [SR-FIXTURES](0115_FIXTURES.md)
7921
-
7922
- **Code**
7923
-
7924
- - [`packages/create-sheetpane/bin/create-sheetpane.mjs`](../packages/create-sheetpane/bin/create-sheetpane.mjs) · [`scripts/create-check.mjs`](../scripts/create-check.mjs)
7925
-
7926
-
7927
- ---
7928
-
7929
- <!-- source: records/0121_VISIBILITY.md (https://github.com/arpitarya/sheetpane/blob/v0.1.0/records/0121_VISIBILITY.md) -->
7887
+ <!-- source: records/0121_VISIBILITY.md (https://github.com/arpitarya/sheetpane/blob/v0.3.0/records/0121_VISIBILITY.md) -->
7930
7888
 
7931
7889
  # SR-VISIBILITY — @sheetpane/visibility
7932
7890