<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: righ</title>
    <description>The latest articles on DEV Community by righ (@righ_48).</description>
    <link>https://dev.to/righ_48</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1395604%2F44434390-17e1-4668-91f7-74745711a95f.png</url>
      <title>DEV Community: righ</title>
      <link>https://dev.to/righ_48</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/righ_48"/>
    <language>en</language>
    <item>
      <title>A Spreadsheet Library Where You Can Build =LLM("question") — GridSheet v3 Release Notes</title>
      <dc:creator>righ</dc:creator>
      <pubDate>Mon, 30 Mar 2026 15:32:25 +0000</pubDate>
      <link>https://dev.to/righ_48/a-spreadsheet-library-where-you-can-build-llmquestion-gridsheet-v3-release-notes-4839</link>
      <guid>https://dev.to/righ_48/a-spreadsheet-library-where-you-can-build-llmquestion-gridsheet-v3-release-notes-4839</guid>
      <description>&lt;p&gt;GridSheet v3 is a major update that fundamentally overhauls the foundation of our spreadsheet UI library. This article covers the key features added since v2.2.0 along with the breaking changes in v3.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv3hnbp40tv11vqzbu803.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv3hnbp40tv11vqzbu803.png" alt="GridSheet preview"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try the demos:&lt;/strong&gt; &lt;a href="https://gridsheet.walkframe.com/examples/case1" rel="noopener noreferrer"&gt;Basic Demo&lt;/a&gt; | &lt;a href="https://gridsheet.walkframe.com/examples/case11" rel="noopener noreferrer"&gt;Async + Spilling Demo&lt;/a&gt; | &lt;a href="https://gridsheet.walkframe.com/getting-started/vanilla#single-html-demo" rel="noopener noreferrer"&gt;Single HTML Demo (CDN)&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;GridSheet works not only with React, but also with Vue, Svelte, and Preact. It can even run in a &lt;strong&gt;single HTML file&lt;/strong&gt; via CDN — no build tools required.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Async Function Support
&lt;/h2&gt;

&lt;p&gt;First-class &lt;strong&gt;async/await support&lt;/strong&gt; for formula functions (introduced in v2.2.0).&lt;/p&gt;

&lt;h3&gt;
  
  
  Background: Designed with LLM and External API Integration in Mind
&lt;/h3&gt;

&lt;p&gt;Integration with external APIs — including LLMs — has become commonplace. If spreadsheet cells could call APIs directly and display or aggregate results, the possibilities for data analysis and workflow automation expand dramatically. The async function support in GridSheet was designed with exactly these use cases in mind.&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;Simply extend &lt;code&gt;BaseFunctionAsync&lt;/code&gt; and make the &lt;code&gt;main()&lt;/code&gt; method &lt;code&gt;async&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FetchWeather&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseFunctionAsync&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;example&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FETCH_WEATHER("Tokyo")&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fetches weather data for the specified city.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;defs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;city&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;City name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}];&lt;/span&gt;
  &lt;span class="nx"&gt;ttlMilliseconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1 minute cache&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.weather.example.com/city=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Per-Cell Cache with Argument Hashing
&lt;/h3&gt;

&lt;p&gt;Async function results are &lt;strong&gt;cached per cell&lt;/strong&gt;. When recalculation runs, the cache key is generated from "function name + argument hash (cyrb53)." If the key matches the previous run, the cached value is returned immediately. If the arguments change, the hash changes, and the function re-executes automatically.&lt;/p&gt;

&lt;p&gt;Setting &lt;code&gt;ttlMilliseconds&lt;/code&gt; adds an expiration to the cache. Expired caches are discarded on the next evaluation, triggering a fresh API call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FetchPrice&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseFunctionAsync&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ttlMilliseconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Expires after 30 seconds → re-fetches&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures &lt;strong&gt;the API is only called for cells whose inputs actually changed&lt;/strong&gt;, minimizing unnecessary network traffic.&lt;/p&gt;

&lt;h3&gt;
  
  
  In-Flight Sharing
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;useInflight&lt;/code&gt; (default: &lt;code&gt;true&lt;/code&gt;) enabled, &lt;strong&gt;when multiple cells simultaneously call the same function with identical arguments, they share a single Promise&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, if both A1 and B1 call &lt;code&gt;=FETCH_WEATHER("Tokyo")&lt;/code&gt;, normally that would be 2 API requests. With in-flight sharing, only 1 request is made. The result from the first Promise is distributed to all cells that referenced it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A1: =FETCH_WEATHER("Tokyo")  ─┐
                               ├─ Same args → Share Promise → 1 API call
B1: =FETCH_WEATHER("Tokyo")  ─┘

C1: =FETCH_WEATHER("Osaka")  ─── Different args → Separate API call
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pending Propagation
&lt;/h3&gt;

&lt;p&gt;Cells that depend on async function results (e.g., &lt;code&gt;=SUM(A1:A3)&lt;/code&gt; where &lt;code&gt;A1&lt;/code&gt; is async) automatically enter Pending state until their dependencies resolve. After resolution, cascade recalculation runs along the dependency graph.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A1: =FETCH_WEATHER("Tokyo")     ← Pending → 25
A2: =FETCH_WEATHER("Osaka")     ← Pending → 28
A3: =AVERAGE(A1:A2)             ← Pending until A1,A2 resolve → 26.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Errors are displayed as &lt;code&gt;#ASYNC!&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Spilling Support
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Spilling&lt;/strong&gt; is a mechanism where a single formula populates multiple cells. When &lt;code&gt;main()&lt;/code&gt; returns a &lt;code&gt;Spilling&lt;/code&gt; object (a 2D array wrapper), values automatically flow into adjacent cells from the origin cell.&lt;/p&gt;

&lt;h3&gt;
  
  
  Synergy with Async Functions
&lt;/h3&gt;

&lt;p&gt;Spilling pairs exceptionally well with async functions, enabling &lt;strong&gt;a single API call to populate multiple columns&lt;/strong&gt;. This is a huge advantage for functions that involve network communication.&lt;/p&gt;

&lt;p&gt;Here's an example that fetches repository info from the GitHub API and expands Stars, Forks, Issues, Size, and Subscribers in one shot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GhRepoFunction&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseFunctionAsync&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;example&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GH_REPO("facebook/react")&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fetches GitHub repo info and spills across multiple columns.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;defs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;repo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Repository in "owner/repo" format&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;acceptedTypes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;}];&lt;/span&gt;
  &lt;span class="nx"&gt;ttlMilliseconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.github.com/repos/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Returns a 1×5 Spilling → auto-expands to columns B–F&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Spilling&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;
      &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stargazers_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forks_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open_issues&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribers_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]]);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the spreadsheet, it looks like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;A (Repository)&lt;/th&gt;
&lt;th&gt;B (Stars)&lt;/th&gt;
&lt;th&gt;C (Forks)&lt;/th&gt;
&lt;th&gt;D (Issues)&lt;/th&gt;
&lt;th&gt;E (Size)&lt;/th&gt;
&lt;th&gt;F (Subscribers)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;facebook/react&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;=GH_REPO(A1)&lt;/code&gt; → spills right&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;vuejs/core&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;=GH_REPO(A2)&lt;/code&gt; →&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;sveltejs/svelte&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;=GH_REPO(A3)&lt;/code&gt; →&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;=SUM(B1:B3)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;=SUM(C1:C3)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;=SUM(D1:D3)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;18 cells of data from just 3 API calls.&lt;/strong&gt; Without Spilling, each cell would need its own API call — instead, one request per repository is all it takes.&lt;/p&gt;

&lt;p&gt;Synchronous functions can also use Spilling by setting &lt;code&gt;autoSpilling = true&lt;/code&gt;, used in matrix functions like &lt;code&gt;TRANSPOSE&lt;/code&gt;, &lt;code&gt;MMULT&lt;/code&gt;, and &lt;code&gt;SEQUENCE&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Enhanced BaseFunction
&lt;/h2&gt;

&lt;p&gt;v3 significantly enhances the &lt;code&gt;BaseFunction&lt;/code&gt; class by introducing &lt;strong&gt;automatic validation generated from type definitions (&lt;code&gt;defs&lt;/code&gt;)&lt;/strong&gt;. This allows inherited functions to focus purely on their logic, with minimal boilerplate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real Example: The COS Function
&lt;/h3&gt;

&lt;p&gt;Here's the actual &lt;code&gt;COS&lt;/code&gt; function implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CosFunction&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseFunction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;example&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;COS(PI())&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Returns the cos of the angle specified in radians.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;defs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FunctionArgumentDefinition&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;angle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;An angle in radians, at which you want the cos.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;acceptedTypes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="nl"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FunctionCategory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;math&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;That's it.&lt;/strong&gt; Checking whether the argument is a number, validating argument count, extracting scalars from cell references (&lt;code&gt;=COS(A1)&lt;/code&gt;) — &lt;code&gt;BaseFunction&lt;/code&gt; handles all of this automatically based on the &lt;code&gt;defs&lt;/code&gt; definition. Developers just write pure logic in &lt;code&gt;main()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  v2 vs v3 Comparison
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// v2: Had to write validation manually&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyFunc&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseFunction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;helpTexts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Returns the sum of two numbers.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a: first number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;b: second number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormulaError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#VALUE!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Please provide numbers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// v3: Just define defs — argument count and type checking are automatic&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyFunc&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseFunction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;example&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MY_FUNC(1, 2)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Returns the sum of two numbers.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FunctionCategory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;math&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;defs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FunctionArgumentDefinition&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;First number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;acceptedTypes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Second number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;acceptedTypes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Pre-validated values are passed in&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Automatic Validation Details
&lt;/h3&gt;

&lt;p&gt;The following is automatically handled based on &lt;code&gt;defs&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;th&gt;Automatic Behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Argument count check&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;defs&lt;/code&gt; length + &lt;code&gt;optional&lt;/code&gt; / &lt;code&gt;variadic&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;#N/A&lt;/code&gt; on too few or too many&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Type checking&lt;/td&gt;
&lt;td&gt;&lt;code&gt;acceptedTypes: ['number', 'string', ...]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;#VALUE!&lt;/code&gt; on mismatch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Variadic arguments&lt;/td&gt;
&lt;td&gt;&lt;code&gt;variadic: true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Accept repeating parameters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Optional arguments&lt;/td&gt;
&lt;td&gt;&lt;code&gt;optional: true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Allow omission&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1x1 matrix extraction&lt;/td&gt;
&lt;td&gt;Default behavior&lt;/td&gt;
&lt;td&gt;Auto-unwrap single cell references to scalar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Matrix passthrough&lt;/td&gt;
&lt;td&gt;&lt;code&gt;takesMatrix: true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Suppress broadcasting, pass matrix as-is&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Percentage string conversion&lt;/td&gt;
&lt;td&gt;Default behavior&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;"50%"&lt;/code&gt; → &lt;code&gt;0.5&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When built-in validation isn't enough, override &lt;code&gt;validate(args)&lt;/code&gt; with custom coercion logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;matrix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extractNumberMatrix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;matrix&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requireSquare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MY_MATRIX_FUNC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;example&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, and &lt;code&gt;category&lt;/code&gt; fields are displayed in the formula autocomplete UI, helping users discover and understand functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Functions Package Separation
&lt;/h2&gt;

&lt;p&gt;In v2, all standard formula functions (&lt;code&gt;SUM&lt;/code&gt;, &lt;code&gt;VLOOKUP&lt;/code&gt;, etc.) were bundled in &lt;code&gt;@gridsheet/core&lt;/code&gt;. As functions grew, the core package became bloated, and unused functions were included in the bundle.&lt;/p&gt;

&lt;p&gt;v3 extracts standard functions into a separate &lt;strong&gt;&lt;code&gt;@gridsheet/functions&lt;/code&gt;&lt;/strong&gt; package with category-based subpath exports.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@gridsheet/functions
├── ./math        (SUM, PRODUCT, SQRT, MOD, ROUND, SUMIF, COUNTIF, ...)  45+ functions
├── ./statistics  (AVERAGE, STDEV, VAR, PERCENTILE, CORREL, ...)         20+ functions
├── ./text        (UPPER, LOWER, TRIM, CONCATENATE, FIND, REPLACE, ...)  20+ functions
├── ./lookup      (VLOOKUP, HLOOKUP, INDEX, MATCH, ...)                  10+ functions
├── ./time        (DATE, TIME, YEAR, MONTH, DAY, HOUR, ...)              15+ functions
├── ./logical     (IF, IFS, XOR, IFNA, ...)                              5+ functions
└── ./information (ISTEXT, ISNUMBER, ISDATE, ISERROR, ...)               Exclude entirely if unneeded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tree-Shaking Support
&lt;/h3&gt;

&lt;p&gt;Import only the categories you need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Only math functions&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;mathFunctions&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@gridsheet/functions/math&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// All functions&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;allFunctions&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@gridsheet/functions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Recommended: register all categories at once with useSpellbook&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useSpellbook&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@gridsheet/react-core/spellbook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSpellbook&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;additionalFunctions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;my_func&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MyFunc&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// → math, statistics, text, lookup, time, logical, information — all available&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core package now focuses on the formula engine, sheet management, and Policy system, preventing bloat from function additions.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Policy Unification
&lt;/h2&gt;

&lt;p&gt;In v2, &lt;code&gt;Renderer&lt;/code&gt; (cell rendering), &lt;code&gt;Parser&lt;/code&gt; (value conversion), &lt;code&gt;Labeler&lt;/code&gt; (header labels), and &lt;code&gt;Policy&lt;/code&gt; (input constraints) existed as separate classes. In practice, these were tightly coupled — for example, "a cell that displays percentages should also parse percentages on input and convert to decimals during formula evaluation" required configuration across multiple classes.&lt;/p&gt;

&lt;p&gt;v3 merges all of these into a single &lt;strong&gt;&lt;code&gt;Policy&lt;/code&gt;&lt;/strong&gt; class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// v2: Configure 3 separate classes&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myRenderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Renderer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;mixins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ThousandSeparatorRendererMixin&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myPolicy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Policy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;mixins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;DropdownPolicyMixin&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// v3: One Policy covers everything&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myPolicy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Policy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;mixins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ThousandSeparatorPolicyMixin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DropdownPolicyMixin&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;PolicyMixinType&lt;/code&gt; covers rendering, serialization, deserialization, scalar conversion, and selection helpers all in one place.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hook&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;render&lt;/code&gt; / &lt;code&gt;renderCallback&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Cell rendering (full custom / wrapper)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;serialize&lt;/code&gt; / &lt;code&gt;deserialize&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Clipboard and initial value conversion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;toScalar&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Scalar conversion during formula evaluation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getSelectOptions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Dropdown choices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;renderRowHeaderLabel&lt;/code&gt; / &lt;code&gt;renderColHeaderLabel&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Header label customization&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;By consolidating into one class, "a cell's behavior" can be defined and reused as a single Policy object. Mixin composition remains supported, allowing you to combine existing behaviors into new Policies.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Function Guide Display
&lt;/h2&gt;

&lt;p&gt;When entering formulas, &lt;strong&gt;function names and argument guides now display in real time&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;During autocomplete&lt;/strong&gt;: Selecting a function candidate shows its category, usage example (&lt;code&gt;example&lt;/code&gt;), description, and each argument's name/type/description&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;While typing&lt;/strong&gt;: Moving the cursor within a function's parentheses highlights the current argument with its type and description. Variadic arguments (&lt;code&gt;variadic&lt;/code&gt;) are supported with proper parameter cycling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These guide details are auto-generated from BaseFunction's &lt;code&gt;example&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;defs[].description&lt;/code&gt;, and &lt;code&gt;defs[].acceptedTypes&lt;/code&gt;. Simply defining a function provides user-facing documentation with zero extra effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Canvas Overlay for Cell State Rendering
&lt;/h2&gt;

&lt;p&gt;v3 migrates cell selection states, copy/cut ranges, autofill, and search highlighting to a &lt;strong&gt;Canvas-based Overlay&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In v2, DOM elements were used for these state representations, but selecting many cells caused DOM node proliferation and high rendering costs. In v3, the &lt;code&gt;CellStateOverlay&lt;/code&gt; component draws all states on a single &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Selection range&lt;/strong&gt;: Blue border with semi-transparent fill&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Active cell&lt;/strong&gt;: 2px bold border&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copy/Cut&lt;/strong&gt;: Dashed border&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Formula references&lt;/strong&gt;: Color palette highlighting for reference ranges&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search matches&lt;/strong&gt;: Green background highlight&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Header highlight&lt;/strong&gt;: Emphasis on selected column/row headers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Draw scheduling via &lt;code&gt;requestAnimationFrame&lt;/code&gt; and synchronous drawing on scroll deliver smooth interaction even with massive datasets (1 million cell demo).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fung8xp6rw0q35hrv35e0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fung8xp6rw0q35hrv35e0.png" alt="Column menu"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Filter and Sort
&lt;/h2&gt;

&lt;p&gt;Column header menus now offer &lt;strong&gt;filter&lt;/strong&gt; and &lt;strong&gt;sort&lt;/strong&gt; capabilities (introduced in v2.2.0).&lt;/p&gt;

&lt;h3&gt;
  
  
  Filter
&lt;/h3&gt;

&lt;p&gt;Combine multiple conditions per column from 10 types: &lt;code&gt;eq&lt;/code&gt;, &lt;code&gt;ne&lt;/code&gt;, &lt;code&gt;gt&lt;/code&gt;, &lt;code&gt;gte&lt;/code&gt;, &lt;code&gt;lt&lt;/code&gt;, &lt;code&gt;lte&lt;/code&gt;, &lt;code&gt;blank&lt;/code&gt;, &lt;code&gt;nonblank&lt;/code&gt;, &lt;code&gt;includes&lt;/code&gt;, &lt;code&gt;excludes&lt;/code&gt; — with AND/OR logic switching. Comparisons are type-aware for numbers, dates, times, and strings.&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;filterFixed: true&lt;/code&gt; on specific rows to keep them always visible regardless of filter conditions — useful for header rows or totals.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sort
&lt;/h3&gt;

&lt;p&gt;Ascending (A→Z) and descending (Z→A) sorting with type-aware comparison. Null/undefined values are placed at the end. Rows with &lt;code&gt;sortFixed: true&lt;/code&gt; are excluded from sorting and maintain their original position.&lt;/p&gt;

&lt;h3&gt;
  
  
  Async Function Integration
&lt;/h3&gt;

&lt;p&gt;Both filter and sort are designed with async functions in mind. When Pending cells exist, operations wait until all async computations complete, then execute based on resolved values. A "Filtering…" / "Sorting…" loading indicator with a cancel button is shown during the wait.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Renames and API Cleanup
&lt;/h2&gt;

&lt;p&gt;v3 unifies naming for clarity and consistency. The biggest change is the rename from &lt;strong&gt;&lt;code&gt;Hub&lt;/code&gt; → &lt;code&gt;Book&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;Table&lt;/code&gt; → &lt;code&gt;Sheet&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Book&lt;/code&gt; (formerly &lt;code&gt;Hub&lt;/code&gt;) is the object passed to &lt;code&gt;GridSheet&lt;/code&gt;'s props. Passing the same &lt;code&gt;book&lt;/code&gt; to multiple &lt;code&gt;&amp;lt;GridSheet&amp;gt;&lt;/code&gt; components shares values across sheets, enabling &lt;strong&gt;cross-sheet references&lt;/strong&gt; (e.g., &lt;code&gt;=Sheet1!A1 + Sheet2!B2&lt;/code&gt;). In v2 this was called &lt;code&gt;Hub&lt;/code&gt;, but "book" and "sheet" are far more natural for the spreadsheet mental model. v3 unifies this terminology across the entire codebase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useSpellbook&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@gridsheet/react-core/spellbook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// useSpellbook = useBook + all standard functions preloaded&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSpellbook&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;additionalFunctions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;my_func&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MyFunc&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;// Add custom functions&lt;/span&gt;
  &lt;span class="na"&gt;policies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;percent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Policy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;mixins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;PercentagePolicyMixin&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Pass the same book to multiple GridSheets for cross-sheet references&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GridSheet&lt;/span&gt; &lt;span class="na"&gt;book&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;sheetName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Sheet1"&lt;/span&gt; &lt;span class="na"&gt;initialCells&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GridSheet&lt;/span&gt; &lt;span class="na"&gt;book&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;book&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;sheetName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Sheet2"&lt;/span&gt; &lt;span class="na"&gt;initialCells&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Related APIs have been consistently renamed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;v2&lt;/th&gt;
&lt;th&gt;v3&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;Hub&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;Book&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multi-sheet management unit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;Table&lt;/code&gt; / &lt;code&gt;UserTable&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;Sheet&lt;/code&gt; / &lt;code&gt;UserSheet&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Individual sheet data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Connector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;SheetRef&lt;/code&gt; / &lt;code&gt;StoreRef&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Clear separation of sheet data vs UI state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cell.system.changedAt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sheet.__raw__.getSystem(point).changedTime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unified naming with sheet-level &lt;code&gt;changedTime&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;helpTexts[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;description&lt;/code&gt; + &lt;code&gt;defs[].description&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Structured function documentation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;Connector&lt;/code&gt; mixed sheet data access and UI state manipulation in a single object. v3 splits this into &lt;code&gt;SheetRef&lt;/code&gt; (sheet data read/write) and &lt;code&gt;StoreRef&lt;/code&gt; (UI control like cursor and selection state), clarifying responsibilities.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// v2&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;connector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useConnector&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;connector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tableManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// v3&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sheetRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSheetRef&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storeRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useStoreRef&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sheetRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Async function support&lt;/td&gt;
&lt;td&gt;First-class support for LLM and external API integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spilling&lt;/td&gt;
&lt;td&gt;Populate multiple cells from a single call; powerful combo with async&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enhanced BaseFunction&lt;/td&gt;
&lt;td&gt;Auto-validation from type definitions; minimal code for functions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Functions package split&lt;/td&gt;
&lt;td&gt;Prevent core bloat; tree-shaking support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Policy unification&lt;/td&gt;
&lt;td&gt;Rendering, conversion, and constraints in one class&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Function guide&lt;/td&gt;
&lt;td&gt;Auto-generated from defs; real-time argument highlighting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canvas Overlay&lt;/td&gt;
&lt;td&gt;Cell state rendering on Canvas; smooth with massive datasets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter and Sort&lt;/td&gt;
&lt;td&gt;Type-aware conditional filter/sort; waits for async completion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Renames and API cleanup&lt;/td&gt;
&lt;td&gt;Clarified responsibilities and consistent naming&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;v3 is a release that lays the foundation for "connecting spreadsheets to the outside world." The combination of async functions and Spilling enables workflows where data fetched from APIs is aggregated and analyzed on a spreadsheet in real time — with minimal code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Development Retrospective
&lt;/h2&gt;

&lt;p&gt;A significant portion of v3 was implemented with Claude's assistance. Routine tasks — batch renames, test scaffolding, function generation — were incredibly fast, condensing days of work into hours.&lt;/p&gt;

&lt;p&gt;However, design remains a challenge for AI. Reviewing proposed designs and iterating on corrections consumed a lot of time. "Tell it &lt;em&gt;how&lt;/em&gt; to build something and it delivers accurately, but the judgment of &lt;em&gt;what to build and how to architect it&lt;/em&gt; still falls on the human side."&lt;/p&gt;

&lt;p&gt;No exaggeration — March was almost entirely spent on this work. Development velocity is definitely up with AI collaboration, but has it gotten easier? Honestly, "the more AI can build quickly, the more you want to do, so it ends up being just as intense."&lt;/p&gt;

&lt;p&gt;That said, Claude (Sonnet) delivers remarkably high-quality code for routine implementation work. When the architectural direction is set, a substantial portion of the implementation can be delegated.&lt;/p&gt;

&lt;p&gt;One caveat: accepting AI-generated code without review builds a Tower of Babel. AI is good at short-term goal achievement — "make this function work," "make this test pass" — but it still sometimes writes ad-hoc code, and maintaining long-term coherence across the codebase is not its strength. For now, it's essential for humans to own the architecture and continuously course-correct through review.&lt;/p&gt;

&lt;p&gt;I'm hopeful that the next round of feature development will allow delegating even more — from design through implementation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/walkframe/gridsheet" rel="noopener noreferrer"&gt;https://github.com/walkframe/gridsheet&lt;/a&gt;&lt;/p&gt;

</description>
      <category>spreadsheet</category>
      <category>javascript</category>
      <category>react</category>
      <category>llm</category>
    </item>
    <item>
      <title>GridSheet v2 – A Modern, Extensible Spreadsheet Engine for the Web</title>
      <dc:creator>righ</dc:creator>
      <pubDate>Mon, 28 Jul 2025 13:49:51 +0000</pubDate>
      <link>https://dev.to/righ_48/gridsheet-v2-a-modern-extensible-spreadsheet-engine-for-the-web-1a69</link>
      <guid>https://dev.to/righ_48/gridsheet-v2-a-modern-extensible-spreadsheet-engine-for-the-web-1a69</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Built with React, now available for Vue and other frameworks via a new Preact build.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧭 What is GridSheet?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/walkframe/gridsheet" rel="noopener noreferrer"&gt;GridSheet&lt;/a&gt; is a highly customizable spreadsheet engine for modern web applications.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://stackblitz.com/edit/gridsheet-demo-zenn?ctl=1&amp;amp;embed=1&amp;amp;file=src%2FApp.tsx&amp;amp;view=preview" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;It supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reactive formula evaluation
&lt;/li&gt;
&lt;li&gt;Range-based calculations and updates
&lt;/li&gt;
&lt;li&gt;Cell-level renderers, editors, and policies
&lt;/li&gt;
&lt;li&gt;Multi-sheet structures with cross-sheet references
&lt;/li&gt;
&lt;li&gt;Visual functions like &lt;code&gt;=SUM(A1:A5)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GridSheet gives you the power of a spreadsheet — not as a black-box widget, but as a developer-first engine you can shape and extend.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ Why I Built It
&lt;/h2&gt;

&lt;p&gt;I originally started working on GridSheet because I needed a &lt;strong&gt;spreadsheet-like UI&lt;/strong&gt; for my own projects — something that felt like Excel, but was fully controllable from React.&lt;/p&gt;

&lt;p&gt;At first, I just wanted a clean table UI that I could edit, render custom components in, and connect to my app’s state.&lt;/p&gt;

&lt;p&gt;But as the needs grew, I gradually added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Support for ranges
&lt;/li&gt;
&lt;li&gt;Dependency tracking
&lt;/li&gt;
&lt;li&gt;Sheet management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eventually, I realized this wasn’t just a spreadsheet-style UI anymore.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;It had become a full spreadsheet engine.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 What’s New in v2
&lt;/h2&gt;

&lt;p&gt;With the release of &lt;strong&gt;GridSheet v2&lt;/strong&gt;, the architecture has matured significantly.&lt;/p&gt;

&lt;p&gt;The core is built in React, and the same codebase is also compiled with Preact — not primarily for size reduction, but to enable integration with frameworks like Vue or Vanilla JS.&lt;/p&gt;

&lt;p&gt;This makes it possible to offer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;Vue wrapper&lt;/strong&gt; built on top of the Preact core&lt;/li&gt;
&lt;li&gt;Support for other environments like &lt;strong&gt;Vanilla JS&lt;/strong&gt; (and potentially more in the future)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GridSheet is no longer tied to React-only apps — it’s becoming a truly flexible engine that can fit into a wide variety of frontend ecosystems.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ The published &lt;code&gt;dist&lt;/code&gt; is a development build — it includes type definitions, source maps, and is not minified.&lt;br&gt;&lt;br&gt;
A local production build results in a much smaller output (e.g. &lt;code&gt;index.js&lt;/code&gt; is around &lt;strong&gt;269KB&lt;/strong&gt;).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧪 Try It
&lt;/h2&gt;

&lt;p&gt;You can see GridSheet in action here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;💻 &lt;a href="https://github.com/walkframe/gridsheet" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🌐 &lt;a href="https://gridsheet.walkframe.com" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whether you’re building a dashboard, internal tool, form builder, or data visualization platform —&lt;br&gt;&lt;br&gt;
&lt;strong&gt;If you’ve ever wanted spreadsheet power inside your own app, I hope you’ll give GridSheet a try.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🗣️ Feedback welcome!
&lt;/h2&gt;

&lt;p&gt;I'd love to hear your thoughts, suggestions, or stories about how you might use GridSheet in your projects.&lt;br&gt;&lt;br&gt;
Let's build better spreadsheets — together.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;💻 &lt;a href="https://github.com/walkframe/gridsheet" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🌐 &lt;a href="https://gridsheet.walkframe.com" rel="noopener noreferrer"&gt;Live Demo &amp;amp; Docs&lt;/a&gt; — includes documentation and additional examples&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>vue</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
