@gui-chat-plugin/spreadsheet 2.1.0 → 3.0.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.
package/README.md CHANGED
@@ -16,12 +16,14 @@ This plugin provides a full-featured spreadsheet component with:
16
16
  - Lookup functions (VLOOKUP, HLOOKUP, INDEX, MATCH)
17
17
  - Financial functions (PMT, PV, FV, NPV, IRR)
18
18
  - Multi-sheet support
19
- - Excel file import/export (.xlsx)
19
+ - Excel file export (.xlsx)
20
+ - Saving to the host's artifacts area, with cell edits written back
21
+ - UI in 8 languages, following the host's locale
20
22
  - Cell formatting and styling
21
23
 
22
24
  ## Important: SheetJS Dependency
23
25
 
24
- > **Note:** This package uses [SheetJS](https://docs.sheetjs.com/docs/getting-started/installation/nodejs/) for Excel file import/export.
26
+ > **Note:** This package uses [SheetJS](https://docs.sheetjs.com/docs/getting-started/installation/nodejs/) for Excel file export.
25
27
  > SheetJS is **not installed from npm**, but directly from the official SheetJS CDN:
26
28
  > ```
27
29
  > https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz
@@ -58,10 +60,37 @@ import "@gui-chat-plugin/spreadsheet/style.css";
58
60
 
59
61
  ```typescript
60
62
  import { pluginCore, TOOL_NAME } from "@gui-chat-plugin/spreadsheet";
61
- // or
62
- import pluginCore from "@gui-chat-plugin/spreadsheet";
63
63
  ```
64
64
 
65
+ ## Saving workbooks (host integration)
66
+
67
+ The plugin works in two modes, chosen by what the host passes to `execute`:
68
+
69
+ - **With `files.artifacts`** (the gui-chat-protocol runtime capability): the workbook is written to `artifacts/spreadsheets/YYYY/MM/<id>.json` and `data.sheets` holds that path. The View reads and saves it through `useRuntime().dispatch`, so cell edits land in the same file and the model can read them back from it.
70
+ - **Without it**: `data.sheets` holds the workbook itself, and edits are reported through `updateResult` only.
71
+
72
+ The View sends two dispatch calls:
73
+
74
+ | `kind` | other args | result |
75
+ |--------|-----------|--------|
76
+ | `loadSpreadsheet` | `path` | `{ data: { sheets } }` |
77
+ | `saveSpreadsheet` | `path`, `sheets` | `{ data: { sheets: path } }` |
78
+
79
+ `path` must be a `.json` file under `artifacts/spreadsheets/`.
80
+
81
+ A host that routes the View's `dispatch` to the plugin's own `execute` (as MulmoTerminal does) needs nothing more: `pluginCore.execute` tells the two calls apart by `kind`. A host with its own dispatch route can call `executeSpreadsheetDispatch(files.artifacts, args)` after checking `isSpreadsheetDispatchArgs(args)`.
82
+
83
+ The View must be rendered inside the host's `PLUGIN_RUNTIME_KEY` provider to load or save a saved workbook; it also takes the UI language from that runtime (`en`, `ja`, `de`, `es`, `fr`, `ko`, `pt-BR`, `zh`, falling back to English).
84
+
85
+ ## Upgrading from 2.x
86
+
87
+ 3.0.0 changes what a host receives, so it is a major release:
88
+
89
+ - `SpreadsheetToolData.sheets` is `SpreadsheetSheet[] | string`. With `files.artifacts` the tool returns the saved file's path instead of the workbook, so code that reads `data.sheets` as an array has to handle the path (or load it with the `loadSpreadsheet` dispatch).
90
+ - `pluginCore.execute` also accepts `SpreadsheetDispatchArgs`.
91
+ - The `gui-chat-protocol` peer is `^2.3.0`, and the package no longer bundles it: the host's copy is used.
92
+ - Cells may hold a boolean `v`, and stored workbooks may contain bare values, `null` gaps and `{}` cells (`StorableCell`).
93
+
65
94
  ## Package Exports
66
95
 
67
96
  | Export | Description |
@@ -0,0 +1,8 @@
1
+ import type { FileOps, ToolResult } from "gui-chat-protocol";
2
+ import type { SpreadsheetDispatchArgs, SpreadsheetToolData } from "./types";
3
+ export declare function isSpreadsheetDispatchArgs(value: unknown): value is SpreadsheetDispatchArgs;
4
+ /** `files.artifacts` off a host context, when the host provides it. The
5
+ * protocol's `ToolContext` does not declare `files`, so it is checked rather
6
+ * than assumed. */
7
+ export declare function artifactsFileOps(context: unknown): FileOps | null;
8
+ export declare function executeSpreadsheetDispatch(artifacts: FileOps, args: SpreadsheetDispatchArgs): Promise<ToolResult<SpreadsheetToolData, never>>;
@@ -2,8 +2,10 @@
2
2
  * Spreadsheet Plugin - Core (Framework-agnostic)
3
3
  *
4
4
  * This module exports the core plugin logic without UI components.
5
- * Import from "@anthropic/guichat-plugin-spreadsheet" or "@anthropic/guichat-plugin-spreadsheet/core"
5
+ * Import from "@gui-chat-plugin/spreadsheet" or "@gui-chat-plugin/spreadsheet/core"
6
6
  */
7
- export type { SpreadsheetCell, SpreadsheetSheet, SpreadsheetToolData, SpreadsheetArgs, } from "./types";
7
+ export type { SpreadsheetCell, SpreadsheetCellValue, StorableCell, SpreadsheetSheet, SpreadsheetToolData, SpreadsheetArgs, SpreadsheetDispatchArgs, } from "./types";
8
8
  export { TOOL_NAME, TOOL_DEFINITION, SYSTEM_PROMPT, executeSpreadsheet, pluginCore, } from "./plugin";
9
+ export { executeSpreadsheetDispatch, isSpreadsheetDispatchArgs } from "./dispatch";
10
+ export { isSpreadsheetArtifactPath } from "./paths";
9
11
  export { samples } from "./samples";
@@ -0,0 +1,13 @@
1
+ export interface SpreadsheetPath {
2
+ /** Relative to the artifacts root — what `files.artifacts` takes. */
3
+ relPath: string;
4
+ /** Workspace-relative — what the tool reports and the View dispatches. */
5
+ filePath: string;
6
+ }
7
+ export declare function spreadsheetArtifactPath(now?: Date, fileId?: string): SpreadsheetPath;
8
+ /** True for a workspace-relative path inside `artifacts/spreadsheets/` ending
9
+ * in `.json`, with no traversal, empty, or backslash segments. */
10
+ export declare function isSpreadsheetArtifactPath(value: unknown): value is string;
11
+ /** `artifacts/spreadsheets/…` → `spreadsheets/…`. Assumes the input passed
12
+ * `isSpreadsheetArtifactPath`. */
13
+ export declare const toArtifactsRelative: (filePath: string) => string;
@@ -5,7 +5,11 @@
5
5
  * Can be used by any framework (Vue, React, etc.)
6
6
  */
7
7
  import type { ToolPluginCore, ToolContext, ToolResult } from "gui-chat-protocol";
8
- import type { SpreadsheetToolData, SpreadsheetArgs } from "./types";
8
+ import type { SpreadsheetToolData, SpreadsheetArgs, SpreadsheetDispatchArgs } from "./types";
9
9
  export { TOOL_NAME, TOOL_DEFINITION, SYSTEM_PROMPT } from "./definition";
10
- export declare const executeSpreadsheet: (_context: ToolContext, args: SpreadsheetArgs) => Promise<ToolResult<SpreadsheetToolData, never>>;
11
- export declare const pluginCore: ToolPluginCore<SpreadsheetToolData, never, SpreadsheetArgs>;
10
+ /**
11
+ * Create a spreadsheet. With the host's `files.artifacts`, the workbook is
12
+ * saved and `data.sheets` carries its path; without it, the workbook itself.
13
+ */
14
+ export declare const executeSpreadsheet: (context: ToolContext, args: SpreadsheetArgs) => Promise<ToolResult<SpreadsheetToolData, never>>;
15
+ export declare const pluginCore: ToolPluginCore<SpreadsheetToolData, never, SpreadsheetArgs | SpreadsheetDispatchArgs>;
@@ -0,0 +1,5 @@
1
+ import type { SpreadsheetSheet } from "./types";
2
+ export declare const isSheet: (value: unknown) => value is SpreadsheetSheet;
3
+ /** Taken whole or rejected whole — dropping the unreadable half of a workbook
4
+ * would hide part of a user's file behind cells that merely look empty. */
5
+ export declare function readSheets(value: unknown): SpreadsheetSheet[] | null;
@@ -4,18 +4,40 @@
4
4
  * Plugin-specific types only.
5
5
  * Common types are imported from gui-chat-protocol.
6
6
  */
7
+ /** Mirrors the engine's `StoredCellValue`. */
8
+ export type SpreadsheetCellValue = string | number | boolean;
7
9
  export interface SpreadsheetCell {
8
- v: string | number;
10
+ v: SpreadsheetCellValue;
9
11
  f?: string;
10
12
  }
13
+ /** A cell as a stored workbook actually holds it. The engine's `normalizeData`
14
+ * folds all four forms, and models and hand edits write all four, so a
15
+ * workbook read back off disk cannot promise `{ v }` for every cell. The tool
16
+ * SCHEMA is deliberately stricter — it says what a caller should emit, not
17
+ * what an existing file may contain. */
18
+ export type StorableCell = SpreadsheetCell | {
19
+ v?: null;
20
+ f?: string;
21
+ } | SpreadsheetCellValue | null;
11
22
  export interface SpreadsheetSheet {
12
23
  name: string;
13
- data: Array<Array<SpreadsheetCell>>;
24
+ data: StorableCell[][];
14
25
  }
26
+ /** `sheets` is the workbook itself, or — when the host gave the plugin
27
+ * `files.artifacts` — the workspace-relative path it was saved to. */
15
28
  export interface SpreadsheetToolData {
16
- sheets: SpreadsheetSheet[];
29
+ sheets: SpreadsheetSheet[] | string;
17
30
  }
18
31
  export interface SpreadsheetArgs {
19
32
  title: string;
20
33
  sheets: SpreadsheetSheet[];
21
34
  }
35
+ /** What the View sends through `useRuntime().dispatch`. */
36
+ export type SpreadsheetDispatchArgs = {
37
+ kind: "loadSpreadsheet";
38
+ path: string;
39
+ } | {
40
+ kind: "saveSpreadsheet";
41
+ path: string;
42
+ sheets: SpreadsheetSheet[];
43
+ };
package/dist/core.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./samples-ClV76j10.cjs");exports.SYSTEM_PROMPT=e.i,exports.TOOL_DEFINITION=e.a,exports.TOOL_NAME=e.o,exports.executeSpreadsheet=e.n,exports.pluginCore=e.r,exports.samples=e.t;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./samples-BzI0GdUj.cjs");exports.SYSTEM_PROMPT=e.d,exports.TOOL_DEFINITION=e.f,exports.TOOL_NAME=e.p,exports.executeSpreadsheet=e.n,exports.executeSpreadsheetDispatch=e.i,exports.isSpreadsheetArtifactPath=e.s,exports.isSpreadsheetDispatchArgs=e.a,exports.pluginCore=e.r,exports.samples=e.t;
package/dist/core.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as e, i as t, n, o as r, r as i, t as a } from "./samples-sKAnErVb.js";
2
- export { t as SYSTEM_PROMPT, e as TOOL_DEFINITION, r as TOOL_NAME, n as executeSpreadsheet, i as pluginCore, a as samples };
1
+ import { a as e, d as t, f as n, i as r, n as i, p as a, r as o, s, t as c } from "./samples-Cylf7cMw.js";
2
+ export { t as SYSTEM_PROMPT, n as TOOL_DEFINITION, a as TOOL_NAME, i as executeSpreadsheet, r as executeSpreadsheetDispatch, s as isSpreadsheetArtifactPath, e as isSpreadsheetDispatchArgs, o as pluginCore, c as samples };
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./samples-ClV76j10.cjs");require("./core.cjs"),exports.SYSTEM_PROMPT=e.i,exports.TOOL_DEFINITION=e.a,exports.TOOL_NAME=e.o,exports.executeSpreadsheet=e.n,exports.pluginCore=e.r,exports.samples=e.t;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./samples-BzI0GdUj.cjs");require("./core.cjs"),exports.SYSTEM_PROMPT=e.d,exports.TOOL_DEFINITION=e.f,exports.TOOL_NAME=e.p,exports.executeSpreadsheet=e.n,exports.executeSpreadsheetDispatch=e.i,exports.isSpreadsheetArtifactPath=e.s,exports.isSpreadsheetDispatchArgs=e.a,exports.pluginCore=e.r,exports.samples=e.t;
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { a as e, i as t, n, o as r, r as i, t as a } from "./samples-sKAnErVb.js";
1
+ import { a as e, d as t, f as n, i as r, n as i, p as a, r as o, s, t as c } from "./samples-Cylf7cMw.js";
2
2
  import "./core.js";
3
- export { t as SYSTEM_PROMPT, e as TOOL_DEFINITION, r as TOOL_NAME, n as executeSpreadsheet, i as pluginCore, a as samples };
3
+ export { t as SYSTEM_PROMPT, n as TOOL_DEFINITION, a as TOOL_NAME, i as executeSpreadsheet, r as executeSpreadsheetDispatch, s as isSpreadsheetArtifactPath, e as isSpreadsheetDispatchArgs, o as pluginCore, c as samples };
@@ -0,0 +1,3 @@
1
+ import type { Messages } from "./messages";
2
+ declare const de: Messages;
3
+ export default de;
@@ -0,0 +1,3 @@
1
+ import type { Messages } from "./messages";
2
+ declare const en: Messages;
3
+ export default en;
@@ -0,0 +1,3 @@
1
+ import type { Messages } from "./messages";
2
+ declare const es: Messages;
3
+ export default es;
@@ -0,0 +1,3 @@
1
+ import type { Messages } from "./messages";
2
+ declare const fr: Messages;
3
+ export default fr;
@@ -0,0 +1 @@
1
+ export declare const useT: () => import("vue").ComputedRef<import("./messages").Messages>;
@@ -0,0 +1,3 @@
1
+ import type { Messages } from "./messages";
2
+ declare const ja: Messages;
3
+ export default ja;
@@ -0,0 +1,3 @@
1
+ import type { Messages } from "./messages";
2
+ declare const ko: Messages;
3
+ export default ko;
@@ -0,0 +1,20 @@
1
+ export interface Messages {
2
+ untitled: string;
3
+ sheetCount(count: number): string;
4
+ excel: string;
5
+ valuePlaceholder: string;
6
+ valueOrFormulaPlaceholder: string;
7
+ formatPlaceholder: string;
8
+ loading: string;
9
+ noData: string;
10
+ editData: string;
11
+ applyChanges: string;
12
+ dataMustBeArray: string;
13
+ loadFailed(error: string): string;
14
+ saveFailed(error: string): string;
15
+ invalidJsonAlert(error: string): string;
16
+ unknownError: string;
17
+ update: string;
18
+ stringType: string;
19
+ formulaType: string;
20
+ }
@@ -0,0 +1,3 @@
1
+ import type { Messages } from "./messages";
2
+ declare const ptBR: Messages;
3
+ export default ptBR;
@@ -0,0 +1,3 @@
1
+ import type { Messages } from "./messages";
2
+ declare const zh: Messages;
3
+ export default zh;
@@ -0,0 +1 @@
1
+ let e=require("gui-chat-protocol");var t=`presentSpreadsheet`,n={type:`function`,name:t,description:`Display an Excel-like spreadsheet with formulas and calculations.`,parameters:{type:`object`,properties:{title:{type:`string`,description:`Title for the spreadsheet`},sheets:{type:`array`,description:`Sheets to render as spreadsheet tabs. Each sheet includes a name and 2D array of cells (rows x columns).`,items:{type:`object`,properties:{name:{type:`string`,description:`Sheet name (e.g., 'Sales Q1', 'Summary')`},data:{type:`array`,description:`Rows of cells. Each cell is an object with 'v' (value) and 'f' (format). Use Excel-style A1 notation in formulas: columns are letters (A, B, C...), rows are 1-based numbers (1, 2, 3...). Values can be text, numbers, dates, or formulas. Examples: [{"v": "Product"}, {"v": 2024, "f": "#,##0"}, {"v": "01/15/2025", "f": "MM/DD/YYYY"}, {"v": "=B2*1.05", "f": "$#,##0.00"}]. Format codes: '$#,##0.00' (currency), '#,##0' (integer), '0.00%' (percent), '0.00' (decimal), 'MM/DD/YYYY' (date), 'DD-MMM-YYYY' (date), 'YYYY-MM-DD' (ISO date).`,items:{type:`array`,description:`Row of cells. Each cell is an object with value and format.`,items:{type:`object`,description:`Cell object with value and optional format. If value is a string starting with '=', it's treated as a formula.`,properties:{v:{oneOf:[{type:`string`},{type:`number`},{type:`boolean`}],description:`Cell value. Can be text, number, date, or formula (string starting with '='). Examples: 'Revenue', 1500000, '01/15/2025', '=SUM(A1:A10)', '=B2-TODAY()'. Date strings like '01/15/2025' are automatically parsed to date serial numbers.`},f:{type:`string`,description:`Optional format code for displaying the value. Common formats: '$#,##0.00' (currency), '#,##0' (integer), '0.00%' (percent), '0.00' (decimal), 'MM/DD/YYYY' (date), 'DD-MMM-YYYY' (date), 'YYYY-MM-DD' (ISO date)`}},required:[`v`]}}}},required:[`name`,`data`]}}},required:[`title`,`sheets`]}},r=`Use ${t} whenever the user needs a spreadsheet-style table, multi-step math, or dynamic what-if analysis—do not summarize in text. Build LIVE sheets where every cell is an object {"v": value, "f": format}. For formulas, set "v" to a string starting with "=" (e.g., {"v": "=B2*1.05", "f": "$#,##0.00"}). For dates, use date strings like "01/15/2025" or date formulas like "=TODAY()" or "=DATE(2025,1,15)". The spreadsheet auto-parses common date formats (MM/DD/YYYY, YYYY-MM-DD, DD-MMM-YYYY) into date serial numbers for calculations. Date arithmetic works: "=B2-TODAY()" calculates days between dates. Never pre-calculate; let the spreadsheet compute using cell refs, functions (SUM, AVERAGE, IF, TODAY, DATE, DATEDIF, etc.), and arithmetic. Standard formats: "$#,##0.00" currency, "#,##0" integer, "0.00%" percent, "0.00" decimal, "MM/DD/YYYY" date, "DD-MMM-YYYY" date, "YYYY-MM-DD" ISO date. Format is optional for plain text/numbers.`;function i(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function a(e){return typeof e==`object`&&!!e}var o=(e,t)=>i(e)&&typeof e[t]==`string`;function s(e,t){return e instanceof Error?e.message:o(e,`details`)&&e.details?e.details:o(e,`message`)&&e.message?e.message:t===void 0?String(e):t}var c=`spreadsheets`,l=`.json`,u=16,d=2,f=e=>`${e.getUTCFullYear()}/${String(e.getUTCMonth()+1).padStart(d,`0`)}`,p=()=>globalThis.crypto.randomUUID().split(`-`).join(``).slice(0,u);function m(t=new Date,n=p()){let r=`${c}/${f(t)}/${n}${l}`;return{relPath:r,filePath:`${e.ARTIFACTS_ROOT}/${r}`}}var h=new Set([``,`.`,`..`]);function g(t){return typeof t!=`string`||!t.startsWith(`${e.ARTIFACTS_ROOT}/${c}/`)||!t.endsWith(l)||t.includes(`\\`)?!1:!t.split(`/`).some(e=>h.has(e))}var _=t=>t.slice(e.ARTIFACTS_ROOT.length+1),v=e=>typeof e==`string`||typeof e==`number`||typeof e==`boolean`,y=e=>e===null||v(e)||i(e)&&(e.v===void 0||e.v===null||v(e.v))&&(e.f===void 0||typeof e.f==`string`),b=e=>Array.isArray(e)&&e.every(y),x=e=>i(e)&&typeof e.name==`string`&&Array.isArray(e.data)&&e.data.every(b);function S(e){return Array.isArray(e)&&e.every(x)?e:null}function C(e){return!i(e)||typeof e.path!=`string`?!1:e.kind===`loadSpreadsheet`||e.kind===`saveSpreadsheet`&&Array.isArray(e.sheets)&&e.sheets.every(x)}var w=e=>i(e)&&typeof e.read==`function`&&typeof e.write==`function`;function T(e){if(!i(e)||!i(e.files))return null;let t=e.files.artifacts;return w(t)?t:null}async function E(e,t){let n=await e.read(_(t)),r=S(JSON.parse(n));if(!r)throw Error(`${t} does not hold an array of sheets`);return{message:`Loaded spreadsheet ${t}`,data:{sheets:r}}}async function D(e,t){if(!g(t.path))throw Error(`path must be a .json file under artifacts/spreadsheets/`);return t.kind===`loadSpreadsheet`?E(e,t.path):(await e.write(_(t.path),JSON.stringify(t.sheets)),{message:`Saved spreadsheet ${t.path}`,data:{sheets:t.path}})}function O(e){if(typeof e!=`string`)return e;try{let t=JSON.parse(e);return console.warn(`Sheets was provided as a string and has been parsed to an array`),t}catch(e){throw Error(`Invalid sheets format: sheets must be an array, not a string. Parse error: ${e instanceof Error?e.message:String(e)}`)}}function k(e){if(!Array.isArray(e)||e.length===0)throw Error(`At least one sheet is required. Sheets must be an array of sheet objects.`);e.forEach(e=>{if(!e.name||!e.data||e.data.length===0)throw Error(`Invalid sheet: ${e.name||`unnamed`}. Each sheet must have a name and data array.`)})}var A=async(e,t)=>{let{title:n}=t,r=O(t.sheets);k(r);let i=`Acknowledge that the spreadsheet has been created and is displayed to the user.`,a=T(e);if(!a)return{message:`Created spreadsheet: ${n}`,title:n,data:{sheets:r},instructions:i};let{relPath:o,filePath:s}=m();return await a.write(o,JSON.stringify(r)),{message:`Created spreadsheet: ${n}. Saved to ${s}; the user can edit cells there, so read that file for the current values.`,title:n,data:{sheets:s},instructions:i}};async function j(e,t){if(!(`kind`in t))return A(e,t);let n=T(e);if(!n)throw Error(`This host does not provide files.artifacts, so spreadsheets cannot be loaded or saved`);if(!C(t))throw Error(`Invalid spreadsheet dispatch`);return D(n,t)}var M={toolDefinition:n,execute:j,generatingMessage:`Creating spreadsheet...`,waitingMessage:`Tell the user that the spreadsheet was created and will be presented shortly.`,isEnabled:()=>!0,systemPrompt:r},N=[{name:`Simple Budget`,args:{title:`Monthly Budget`,sheets:[{name:`Budget`,data:[[{v:`Category`},{v:`Amount`}],[{v:`Income`},{v:5e3,f:`$#,##0.00`}],[{v:`Rent`},{v:-1500,f:`$#,##0.00`}],[{v:`Utilities`},{v:-200,f:`$#,##0.00`}],[{v:`Food`},{v:-600,f:`$#,##0.00`}],[{v:`Transport`},{v:-300,f:`$#,##0.00`}],[{v:`Total`},{v:`=SUM(B2:B6)`,f:`$#,##0.00`}]]}]}},{name:`Sales Report`,args:{title:`Q1 Sales Report`,sheets:[{name:`Sales`,data:[[{v:`Product`},{v:`Jan`},{v:`Feb`},{v:`Mar`},{v:`Total`}],[{v:`Widget A`},{v:1200},{v:1350},{v:1500},{v:`=SUM(B2:D2)`}],[{v:`Widget B`},{v:800},{v:920},{v:1100},{v:`=SUM(B3:D3)`}],[{v:`Widget C`},{v:450},{v:480},{v:520},{v:`=SUM(B4:D4)`}],[{v:`Total`},{v:`=SUM(B2:B4)`},{v:`=SUM(C2:C4)`},{v:`=SUM(D2:D4)`},{v:`=SUM(E2:E4)`}]]}]}},{name:`Loan Calculator`,args:{title:`Loan Calculator`,sheets:[{name:`Calculator`,data:[[{v:`Loan Amount`},{v:25e4,f:`$#,##0`}],[{v:`Interest Rate`},{v:.065,f:`0.00%`}],[{v:`Term (Years)`},{v:30}],[{v:``},{v:``}],[{v:`Monthly Payment`},{v:`=PMT(B2/12, B3*12, -B1)`,f:`$#,##0.00`}],[{v:`Total Interest`},{v:`=B5*B3*12-B1`,f:`$#,##0.00`}]]}]}},{name:`World Population`,args:{title:`世界人口(上位10)`,sheets:[{name:`世界人口(上位10)`,data:[[{v:`順位`},{v:`国・地域`},{v:`人口(概数)`,f:`#,##0`},{v:`上位10合計に占める割合`},{v:`世界人口に占める割合`}],[{v:1,f:`#,##0`},{v:`インド`},{v:1428627e3,f:`#,##0`},{v:`=C2/$C$12`,f:`0.00%`},{v:`=C2/$C$13`,f:`0.00%`}],[{v:2,f:`#,##0`},{v:`中国`},{v:1425671e3,f:`#,##0`},{v:`=C3/$C$12`,f:`0.00%`},{v:`=C3/$C$13`,f:`0.00%`}],[{v:3,f:`#,##0`},{v:`アメリカ合衆国`},{v:334e6,f:`#,##0`},{v:`=C4/$C$12`,f:`0.00%`},{v:`=C4/$C$13`,f:`0.00%`}],[{v:4,f:`#,##0`},{v:`インドネシア`},{v:277e6,f:`#,##0`},{v:`=C5/$C$12`,f:`0.00%`},{v:`=C5/$C$13`,f:`0.00%`}],[{v:5,f:`#,##0`},{v:`パキスタン`},{v:24e7,f:`#,##0`},{v:`=C6/$C$12`,f:`0.00%`},{v:`=C6/$C$13`,f:`0.00%`}],[{v:6,f:`#,##0`},{v:`ナイジェリア`},{v:219e6,f:`#,##0`},{v:`=C7/$C$12`,f:`0.00%`},{v:`=C7/$C$13`,f:`0.00%`}],[{v:7,f:`#,##0`},{v:`ブラジル`},{v:216e6,f:`#,##0`},{v:`=C8/$C$12`,f:`0.00%`},{v:`=C8/$C$13`,f:`0.00%`}],[{v:8,f:`#,##0`},{v:`バングラデシュ`},{v:172e6,f:`#,##0`},{v:`=C9/$C$12`,f:`0.00%`},{v:`=C9/$C$13`,f:`0.00%`}],[{v:9,f:`#,##0`},{v:`ロシア`},{v:146e6,f:`#,##0`},{v:`=C10/$C$12`,f:`0.00%`},{v:`=C10/$C$13`,f:`0.00%`}],[{v:10,f:`#,##0`},{v:`メキシコ`},{v:13e7,f:`#,##0`},{v:`=C11/$C$12`,f:`0.00%`},{v:`=C11/$C$13`,f:`0.00%`}],[{v:``},{v:`合計(上位10)`},{v:`=SUM(C2:C11)`,f:`#,##0`},{v:``},{v:``}],[{v:``},{v:`世界推定人口(例)`},{v:805e7,f:`#,##0`},{v:``},{v:``}]]}]}}];Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return C}}),Object.defineProperty(exports,"c",{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,"d",{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,"f",{enumerable:!0,get:function(){return n}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return D}}),Object.defineProperty(exports,"l",{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return A}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return S}}),Object.defineProperty(exports,"p",{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return M}}),Object.defineProperty(exports,"s",{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return N}}),Object.defineProperty(exports,"u",{enumerable:!0,get:function(){return i}});
@@ -1,7 +1,8 @@
1
+ import { ARTIFACTS_ROOT as e } from "gui-chat-protocol";
1
2
  //#region src/core/definition.ts
2
- var e = "presentSpreadsheet", t = {
3
+ var t = "presentSpreadsheet", n = {
3
4
  type: "function",
4
- name: e,
5
+ name: t,
5
6
  description: "Display an Excel-like spreadsheet with formulas and calculations.",
6
7
  parameters: {
7
8
  type: "object",
@@ -31,7 +32,11 @@ var e = "presentSpreadsheet", t = {
31
32
  description: "Cell object with value and optional format. If value is a string starting with '=', it's treated as a formula.",
32
33
  properties: {
33
34
  v: {
34
- oneOf: [{ type: "string" }, { type: "number" }],
35
+ oneOf: [
36
+ { type: "string" },
37
+ { type: "number" },
38
+ { type: "boolean" }
39
+ ],
35
40
  description: "Cell value. Can be text, number, date, or formula (string starting with '='). Examples: 'Revenue', 1500000, '01/15/2025', '=SUM(A1:A10)', '=B2-TODAY()'. Date strings like '01/15/2025' are automatically parsed to date serial numbers."
36
41
  },
37
42
  f: {
@@ -50,29 +55,117 @@ var e = "presentSpreadsheet", t = {
50
55
  },
51
56
  required: ["title", "sheets"]
52
57
  }
53
- }, n = `Use ${e} whenever the user needs a spreadsheet-style table, multi-step math, or dynamic what-if analysis—do not summarize in text. Build LIVE sheets where every cell is an object {"v": value, "f": format}. For formulas, set "v" to a string starting with "=" (e.g., {"v": "=B2*1.05", "f": "$#,##0.00"}). For dates, use date strings like "01/15/2025" or date formulas like "=TODAY()" or "=DATE(2025,1,15)". The spreadsheet auto-parses common date formats (MM/DD/YYYY, YYYY-MM-DD, DD-MMM-YYYY) into date serial numbers for calculations. Date arithmetic works: "=B2-TODAY()" calculates days between dates. Never pre-calculate; let the spreadsheet compute using cell refs, functions (SUM, AVERAGE, IF, TODAY, DATE, DATEDIF, etc.), and arithmetic. Standard formats: "$#,##0.00" currency, "#,##0" integer, "0.00%" percent, "0.00" decimal, "MM/DD/YYYY" date, "DD-MMM-YYYY" date, "YYYY-MM-DD" ISO date. Format is optional for plain text/numbers.`, r = async (e, t) => {
54
- let { title: n } = t, { sheets: r } = t;
55
- if (typeof r == "string") try {
56
- r = JSON.parse(r), console.warn("Sheets was provided as a string and has been parsed to an array");
58
+ }, r = `Use ${t} whenever the user needs a spreadsheet-style table, multi-step math, or dynamic what-if analysis—do not summarize in text. Build LIVE sheets where every cell is an object {"v": value, "f": format}. For formulas, set "v" to a string starting with "=" (e.g., {"v": "=B2*1.05", "f": "$#,##0.00"}). For dates, use date strings like "01/15/2025" or date formulas like "=TODAY()" or "=DATE(2025,1,15)". The spreadsheet auto-parses common date formats (MM/DD/YYYY, YYYY-MM-DD, DD-MMM-YYYY) into date serial numbers for calculations. Date arithmetic works: "=B2-TODAY()" calculates days between dates. Never pre-calculate; let the spreadsheet compute using cell refs, functions (SUM, AVERAGE, IF, TODAY, DATE, DATEDIF, etc.), and arithmetic. Standard formats: "$#,##0.00" currency, "#,##0" integer, "0.00%" percent, "0.00" decimal, "MM/DD/YYYY" date, "DD-MMM-YYYY" date, "YYYY-MM-DD" ISO date. Format is optional for plain text/numbers.`;
59
+ //#endregion
60
+ //#region src/engine/guards.ts
61
+ function i(e) {
62
+ return typeof e == "object" && !!e && !Array.isArray(e);
63
+ }
64
+ function a(e) {
65
+ return typeof e == "object" && !!e;
66
+ }
67
+ var o = (e, t) => i(e) && typeof e[t] == "string";
68
+ function s(e, t) {
69
+ return e instanceof Error ? e.message : o(e, "details") && e.details ? e.details : o(e, "message") && e.message ? e.message : t === void 0 ? String(e) : t;
70
+ }
71
+ //#endregion
72
+ //#region src/core/paths.ts
73
+ var c = "spreadsheets", l = ".json", u = 16, d = 2, f = (e) => `${e.getUTCFullYear()}/${String(e.getUTCMonth() + 1).padStart(d, "0")}`, p = () => globalThis.crypto.randomUUID().split("-").join("").slice(0, u);
74
+ function m(t = /* @__PURE__ */ new Date(), n = p()) {
75
+ let r = `${c}/${f(t)}/${n}${l}`;
76
+ return {
77
+ relPath: r,
78
+ filePath: `${e}/${r}`
79
+ };
80
+ }
81
+ var h = /* @__PURE__ */ new Set([
82
+ "",
83
+ ".",
84
+ ".."
85
+ ]);
86
+ function g(t) {
87
+ return typeof t != "string" || !t.startsWith(`${e}/${c}/`) || !t.endsWith(l) || t.includes("\\") ? !1 : !t.split("/").some((e) => h.has(e));
88
+ }
89
+ var _ = (t) => t.slice(e.length + 1), v = (e) => typeof e == "string" || typeof e == "number" || typeof e == "boolean", y = (e) => e === null || v(e) || i(e) && (e.v === void 0 || e.v === null || v(e.v)) && (e.f === void 0 || typeof e.f == "string"), b = (e) => Array.isArray(e) && e.every(y), x = (e) => i(e) && typeof e.name == "string" && Array.isArray(e.data) && e.data.every(b);
90
+ function S(e) {
91
+ return Array.isArray(e) && e.every(x) ? e : null;
92
+ }
93
+ //#endregion
94
+ //#region src/core/dispatch.ts
95
+ function C(e) {
96
+ return !i(e) || typeof e.path != "string" ? !1 : e.kind === "loadSpreadsheet" || e.kind === "saveSpreadsheet" && Array.isArray(e.sheets) && e.sheets.every(x);
97
+ }
98
+ var w = (e) => i(e) && typeof e.read == "function" && typeof e.write == "function";
99
+ function T(e) {
100
+ if (!i(e) || !i(e.files)) return null;
101
+ let t = e.files.artifacts;
102
+ return w(t) ? t : null;
103
+ }
104
+ async function E(e, t) {
105
+ let n = await e.read(_(t)), r = S(JSON.parse(n));
106
+ if (!r) throw Error(`${t} does not hold an array of sheets`);
107
+ return {
108
+ message: `Loaded spreadsheet ${t}`,
109
+ data: { sheets: r }
110
+ };
111
+ }
112
+ async function D(e, t) {
113
+ if (!g(t.path)) throw Error("path must be a .json file under artifacts/spreadsheets/");
114
+ return t.kind === "loadSpreadsheet" ? E(e, t.path) : (await e.write(_(t.path), JSON.stringify(t.sheets)), {
115
+ message: `Saved spreadsheet ${t.path}`,
116
+ data: { sheets: t.path }
117
+ });
118
+ }
119
+ //#endregion
120
+ //#region src/core/plugin.ts
121
+ function O(e) {
122
+ if (typeof e != "string") return e;
123
+ try {
124
+ let t = JSON.parse(e);
125
+ return console.warn("Sheets was provided as a string and has been parsed to an array"), t;
57
126
  } catch (e) {
58
127
  throw Error(`Invalid sheets format: sheets must be an array, not a string. Parse error: ${e instanceof Error ? e.message : String(e)}`);
59
128
  }
60
- if (!Array.isArray(r) || r.length === 0) throw Error("At least one sheet is required. Sheets must be an array of sheet objects.");
61
- for (let e of r) if (!e.name || !e.data || e.data.length === 0) throw Error(`Invalid sheet: ${e.name || "unnamed"}. Each sheet must have a name and data array.`);
62
- return {
129
+ }
130
+ function k(e) {
131
+ if (!Array.isArray(e) || e.length === 0) throw Error("At least one sheet is required. Sheets must be an array of sheet objects.");
132
+ e.forEach((e) => {
133
+ if (!e.name || !e.data || e.data.length === 0) throw Error(`Invalid sheet: ${e.name || "unnamed"}. Each sheet must have a name and data array.`);
134
+ });
135
+ }
136
+ var A = async (e, t) => {
137
+ let { title: n } = t, r = O(t.sheets);
138
+ k(r);
139
+ let i = "Acknowledge that the spreadsheet has been created and is displayed to the user.", a = T(e);
140
+ if (!a) return {
63
141
  message: `Created spreadsheet: ${n}`,
64
142
  title: n,
65
143
  data: { sheets: r },
66
- instructions: "Acknowledge that the spreadsheet has been created and is displayed to the user."
144
+ instructions: i
145
+ };
146
+ let { relPath: o, filePath: s } = m();
147
+ return await a.write(o, JSON.stringify(r)), {
148
+ message: `Created spreadsheet: ${n}. Saved to ${s}; the user can edit cells there, so read that file for the current values.`,
149
+ title: n,
150
+ data: { sheets: s },
151
+ instructions: i
67
152
  };
68
- }, i = {
69
- toolDefinition: t,
70
- execute: r,
153
+ };
154
+ async function j(e, t) {
155
+ if (!("kind" in t)) return A(e, t);
156
+ let n = T(e);
157
+ if (!n) throw Error("This host does not provide files.artifacts, so spreadsheets cannot be loaded or saved");
158
+ if (!C(t)) throw Error("Invalid spreadsheet dispatch");
159
+ return D(n, t);
160
+ }
161
+ var M = {
162
+ toolDefinition: n,
163
+ execute: j,
71
164
  generatingMessage: "Creating spreadsheet...",
72
165
  waitingMessage: "Tell the user that the spreadsheet was created and will be presented shortly.",
73
166
  isEnabled: () => !0,
74
- systemPrompt: n
75
- }, a = [
167
+ systemPrompt: r
168
+ }, N = [
76
169
  {
77
170
  name: "Simple Budget",
78
171
  args: {
@@ -417,4 +510,4 @@ var e = "presentSpreadsheet", t = {
417
510
  }
418
511
  ];
419
512
  //#endregion
420
- export { t as a, n as i, r as n, e as o, i as r, a as t };
513
+ export { C as a, s as c, r as d, n as f, D as i, a as l, A as n, S as o, t as p, M as r, g as s, N as t, i as u };
package/dist/style.css CHANGED
@@ -1,3 +1,3 @@
1
1
  /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
2
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-900:oklch(39.3% .095 152.535);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-800:oklch(27.8% .033 256.848);--color-white:#fff;--spacing:.25rem;--container-4xl:56rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-medium:500;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.row-2{grid-row:2}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.mt-1{margin-top:var(--spacing)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.inline{display:inline}.table{display:table}.h-\[600px\]{height:600px}.max-h-64{max-height:calc(var(--spacing) * 64)}.min-h-full{min-height:100%}.max-w-4xl{max-width:var(--container-4xl)}.max-w-\[200px\]{max-width:200px}.flex-shrink{flex-shrink:1}.border-collapse{border-collapse:collapse}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-pointer{cursor:pointer}.resize{resize:both}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing) * 2)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-gray-200{border-color:var(--color-gray-200)}.border-green-200{border-color:var(--color-green-200)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-white{background-color:var(--color-white)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-8{padding:calc(var(--spacing) * 8)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-2{padding-block:calc(var(--spacing) * 2)}.text-center{text-align:center}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-800{color:var(--color-gray-800)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.capitalize{text-transform:capitalize}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (hover:hover){.hover\:border-green-300:hover{border-color:var(--color-green-300)}.hover\:bg-green-200:hover{background-color:var(--color-green-200)}}@media (prefers-color-scheme:dark){.dark\:bg-green-900{background-color:var(--color-green-900)}.dark\:text-gray-200{color:var(--color-gray-200)}.dark\:text-gray-400{color:var(--color-gray-400)}.dark\:text-green-300{color:var(--color-green-300)}}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}.spreadsheet-container[data-v-1d9beb6d]{background:#fff;flex-direction:column;width:100%;height:100%;display:flex}.spreadsheet-content-wrapper[data-v-1d9beb6d]{flex:1;min-height:0;overflow-y:auto}.header[data-v-1d9beb6d]{justify-content:space-between;align-items:center;margin-bottom:1em;display:flex}.title[data-v-1d9beb6d]{margin:0;font-size:2em;font-weight:700}.button-group[data-v-1d9beb6d]{gap:.5em;display:flex}.download-btn[data-v-1d9beb6d]{color:#fff;cursor:pointer;border:none;border-radius:4px;align-items:center;gap:.5em;padding:.5em 1em;font-size:.9em;transition:background-color .2s;display:flex}.excel-btn[data-v-1d9beb6d]{background-color:#217346}.excel-btn[data-v-1d9beb6d]:hover{background-color:#1e6a3f}.excel-btn[data-v-1d9beb6d]:active{background-color:#1a5c36}.download-btn .material-icons[data-v-1d9beb6d]{font-size:1.2em}.sheet-tabs[data-v-1d9beb6d]{border-bottom:2px solid #e0e0e0;gap:.25em;margin-bottom:1em;display:flex}.sheet-tab[data-v-1d9beb6d]{cursor:pointer;color:#666;background:#f5f5f5;border:none;border-top-left-radius:4px;border-top-right-radius:4px;padding:.5em 1em;font-size:.9em;transition:background-color .2s}.sheet-tab[data-v-1d9beb6d]:hover{background:#e8e8e8}.sheet-tab.active[data-v-1d9beb6d]{color:#333;background:#fff;border-bottom:2px solid #fff;margin-bottom:-2px;font-weight:500}.table-container[data-v-1d9beb6d]{background:#fff;overflow-x:auto}.table-container[data-v-1d9beb6d] table{border-collapse:collapse;width:100%;font-family:Segoe UI,Arial,sans-serif;font-size:.9em}.table-container[data-v-1d9beb6d] td,.table-container[data-v-1d9beb6d] th{text-align:left;border:1px solid #d0d0d0;padding:.5em .75em}.table-container[data-v-1d9beb6d] th{color:#333;background-color:#f5f5f5;font-weight:600}.table-container[data-v-1d9beb6d] tr:nth-child(2n){background-color:#fafafa}.table-container[data-v-1d9beb6d] tr:hover{background-color:#f0f0f0}.table-container[data-v-1d9beb6d] .cell-editing{outline-offset:-2px;background-color:#e8f5e9!important;outline:2px solid #217346!important}.table-container[data-v-1d9beb6d] .cell-referenced{outline-offset:-2px;background-color:#fff3e0!important;outline:2px solid #ff9800!important}.error[data-v-1d9beb6d]{color:#c62828;background:#ffebee;border-radius:4px;margin:1em 0;padding:1em}.spreadsheet-source[data-v-1d9beb6d]{background:#f5f5f5;border-top:1px solid #e0e0e0;flex-shrink:0;padding:.5rem;font-family:monospace;font-size:.85rem}.spreadsheet-source summary[data-v-1d9beb6d]{cursor:pointer;-webkit-user-select:none;user-select:none;color:#333;background:#e8e8e8;border-radius:4px;padding:.5rem;font-weight:500}.spreadsheet-source[open] summary[data-v-1d9beb6d]{margin-bottom:.5rem}.spreadsheet-source summary[data-v-1d9beb6d]:hover{background:#d8d8d8}.spreadsheet-editor[data-v-1d9beb6d]{color:#333;resize:vertical;background:#fff;border:1px solid #ccc;border-radius:4px;width:100%;height:40vh;margin-bottom:.5rem;padding:1rem;font-family:Courier New,monospace;font-size:.9rem;line-height:1.5}.spreadsheet-editor[data-v-1d9beb6d]:focus{border-color:#217346;outline:none;box-shadow:0 0 0 2px #2173461a}.apply-btn[data-v-1d9beb6d]{color:#fff;cursor:pointer;background:#217346;border:none;border-radius:4px;padding:.5rem 1rem;font-size:.9rem;font-weight:500;transition:background .2s}.apply-btn[data-v-1d9beb6d]:hover{background:#1e6a3f}.apply-btn[data-v-1d9beb6d]:active{background:#1a5c36}.apply-btn[data-v-1d9beb6d]:disabled{color:#666;cursor:not-allowed;opacity:.6;background:#ccc}.apply-btn[data-v-1d9beb6d]:disabled:hover{background:#ccc}.mini-editor-panel[data-v-1d9beb6d]{background:#f8f8f8;border-top:1px solid #d0d0d0;flex-shrink:0}.mini-editor-content[data-v-1d9beb6d]{align-items:center;gap:.5rem;padding:.5rem 1rem;display:flex}.cell-ref[data-v-1d9beb6d]{color:#217346;min-width:2rem;font-family:monospace;font-size:.85rem;font-weight:600}.radio-group[data-v-1d9beb6d]{border-right:1px solid #d0d0d0;gap:.75rem;padding-right:.75rem;display:flex}.radio-option[data-v-1d9beb6d]{cursor:pointer;color:#555;align-items:center;gap:.25rem;font-size:.85rem;display:flex}.radio-option input[type=radio][data-v-1d9beb6d]{cursor:pointer;width:.9rem;height:.9rem}.form-input[data-v-1d9beb6d]{border:1px solid #ccc;border-radius:3px;flex:1;min-width:120px;padding:.4rem .6rem;font-family:inherit;font-size:.85rem;transition:border-color .2s}.form-input[data-v-1d9beb6d]:focus{border-color:#217346;outline:none;box-shadow:0 0 0 2px #2173461a}.form-input[data-v-1d9beb6d]::placeholder{color:#999;font-size:.8rem}.save-btn[data-v-1d9beb6d],.cancel-btn[data-v-1d9beb6d]{cursor:pointer;border:none;border-radius:3px;padding:.4rem .8rem;font-size:.85rem;font-weight:500;transition:background .2s}.save-btn[data-v-1d9beb6d]{color:#fff;background:#217346}.save-btn[data-v-1d9beb6d]:hover{background:#1e6a3f}.cancel-btn[data-v-1d9beb6d]{color:#666;background:0 0;padding:.4rem;font-size:1.2rem;line-height:1}.cancel-btn[data-v-1d9beb6d]:hover{color:#333;background:#e0e0e0}
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-700:oklch(52.7% .154 150.069);--color-green-900:oklch(39.3% .095 152.535);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-800:oklch(27.8% .033 256.848);--color-white:#fff;--spacing:.25rem;--container-4xl:56rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-medium:500;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.row-2{grid-row:2}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.mt-1{margin-top:var(--spacing)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.inline{display:inline}.table{display:table}.h-8{height:calc(var(--spacing) * 8)}.h-\[600px\]{height:600px}.max-h-64{max-height:calc(var(--spacing) * 64)}.min-h-full{min-height:100%}.max-w-4xl{max-width:var(--container-4xl)}.max-w-\[200px\]{max-width:200px}.flex-shrink{flex-shrink:1}.border-collapse{border-collapse:collapse}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-pointer{cursor:pointer}.resize{resize:both}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.gap-1{gap:var(--spacing)}.gap-2{gap:calc(var(--spacing) * 2)}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-gray-200{border-color:var(--color-gray-200)}.border-green-200{border-color:var(--color-green-200)}.bg-\[\#217346\]{background-color:#217346}.bg-gray-100{background-color:var(--color-gray-100)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-white{background-color:var(--color-white)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-8{padding:calc(var(--spacing) * 8)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-2{padding-block:calc(var(--spacing) * 2)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.break-words{overflow-wrap:break-word}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-800{color:var(--color-gray-800)}.text-green-700{color:var(--color-green-700)}.text-white{color:var(--color-white)}.capitalize{text-transform:capitalize}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (hover:hover){.hover\:border-green-300:hover{border-color:var(--color-green-300)}.hover\:bg-\[\#1e6a3f\]:hover{background-color:#1e6a3f}.hover\:bg-green-200:hover{background-color:var(--color-green-200)}}.active\:bg-\[\#1a5c36\]:active{background-color:#1a5c36}@media (prefers-color-scheme:dark){.dark\:bg-green-900{background-color:var(--color-green-900)}.dark\:text-gray-200{color:var(--color-gray-200)}.dark\:text-gray-400{color:var(--color-gray-400)}}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}.spreadsheet-container[data-v-9281886a]{background:#fff;flex-direction:column;width:100%;height:100%;display:flex}.spreadsheet-content-wrapper[data-v-9281886a]{flex:1;min-height:0;overflow-y:auto}.header[data-v-9281886a]{justify-content:space-between;align-items:center;margin-bottom:1em;display:flex}.title[data-v-9281886a]{margin:0;font-size:2em;font-weight:700}.sheet-tabs[data-v-9281886a]{border-bottom:2px solid #e0e0e0;gap:.25em;margin-bottom:1em;display:flex}.sheet-tab[data-v-9281886a]{cursor:pointer;color:#666;background:#f5f5f5;border:none;border-top-left-radius:4px;border-top-right-radius:4px;padding:.5em 1em;font-size:.9em;transition:background-color .2s}.sheet-tab[data-v-9281886a]:hover{background:#e8e8e8}.sheet-tab.active[data-v-9281886a]{color:#333;background:#fff;border-bottom:2px solid #fff;margin-bottom:-2px;font-weight:500}.table-container[data-v-9281886a]{background:#fff;overflow-x:auto}.table-container[data-v-9281886a] table{border-collapse:collapse;width:100%;font-family:Segoe UI,Arial,sans-serif;font-size:.9em}.table-container[data-v-9281886a] td,.table-container[data-v-9281886a] th{text-align:left;border:1px solid #d0d0d0;padding:.5em .75em}.table-container[data-v-9281886a] th{color:#333;background-color:#f5f5f5;font-weight:600}.table-container[data-v-9281886a] tr:nth-child(2n){background-color:#fafafa}.table-container[data-v-9281886a] tr:hover{background-color:#f0f0f0}.table-container[data-v-9281886a] .cell-editing{outline-offset:-2px;background-color:#e8f5e9!important;outline:2px solid #217346!important}.table-container[data-v-9281886a] .cell-referenced{outline-offset:-2px;background-color:#fff3e0!important;outline:2px solid #ff9800!important}.error[data-v-9281886a]{color:#c62828;background:#ffebee;border-radius:4px;margin:1em 0;padding:1em}.spreadsheet-source[data-v-9281886a]{background:#f5f5f5;border-top:1px solid #e0e0e0;flex-shrink:0;padding:.5rem;font-family:Consolas,MS Gothic,BIZ UDGothic,monospace;font-size:.85rem}.spreadsheet-source summary[data-v-9281886a]{cursor:pointer;-webkit-user-select:none;user-select:none;color:#333;background:#e8e8e8;border-radius:4px;padding:.5rem;font-weight:500}.spreadsheet-source[open] summary[data-v-9281886a]{margin-bottom:.5rem}.spreadsheet-source summary[data-v-9281886a]:hover{background:#d8d8d8}.spreadsheet-editor[data-v-9281886a]{color:#333;resize:vertical;background:#fff;border:1px solid #ccc;border-radius:4px;width:100%;height:40vh;margin-bottom:.5rem;padding:1rem;font-family:Courier New,MS Gothic,BIZ UDGothic,monospace;font-size:.9rem;line-height:1.5}.spreadsheet-editor[data-v-9281886a]:focus{border-color:#217346;outline:none;box-shadow:0 0 0 2px #2173461a}.apply-btn[data-v-9281886a]{color:#fff;cursor:pointer;background:#217346;border:none;border-radius:4px;padding:.5rem 1rem;font-size:.9rem;font-weight:500;transition:background .2s}.apply-btn[data-v-9281886a]:hover{background:#1e6a3f}.apply-btn[data-v-9281886a]:active{background:#1a5c36}.apply-btn[data-v-9281886a]:disabled{color:#666;cursor:not-allowed;opacity:.6;background:#ccc}.apply-btn[data-v-9281886a]:disabled:hover{background:#ccc}.mini-editor-panel[data-v-9281886a]{background:#f8f8f8;border-top:1px solid #d0d0d0;flex-shrink:0}.mini-editor-content[data-v-9281886a]{align-items:center;gap:.5rem;padding:.5rem 1rem;display:flex}.cell-ref[data-v-9281886a]{color:#217346;min-width:2rem;font-family:Consolas,MS Gothic,BIZ UDGothic,monospace;font-size:.85rem;font-weight:600}.radio-group[data-v-9281886a]{border-right:1px solid #d0d0d0;gap:.75rem;padding-right:.75rem;display:flex}.radio-option[data-v-9281886a]{cursor:pointer;color:#555;align-items:center;gap:.25rem;font-size:.85rem;display:flex}.radio-option input[type=radio][data-v-9281886a]{cursor:pointer;width:.9rem;height:.9rem}.form-input[data-v-9281886a]{border:1px solid #ccc;border-radius:3px;flex:1;min-width:120px;padding:.4rem .6rem;font-family:inherit;font-size:.85rem;transition:border-color .2s}.form-input[data-v-9281886a]:focus{border-color:#217346;outline:none;box-shadow:0 0 0 2px #2173461a}.form-input[data-v-9281886a]::placeholder{color:#999;font-size:.8rem}.save-btn[data-v-9281886a],.cancel-btn[data-v-9281886a]{cursor:pointer;border:none;border-radius:3px;padding:.4rem .8rem;font-size:.85rem;font-weight:500;transition:background .2s}.save-btn[data-v-9281886a]{color:#fff;background:#217346}.save-btn[data-v-9281886a]:hover{background:#1e6a3f}.cancel-btn[data-v-9281886a]{color:#666;background:0 0;padding:.4rem;font-size:1.2rem;line-height:1}.cancel-btn[data-v-9281886a]:hover{color:#333;background:#e0e0e0}
3
3
  /*$vite$:1*/
@@ -1,4 +1,4 @@
1
- import type { ToolResult } from "gui-chat-protocol";
1
+ import { type ToolResult } from "gui-chat-protocol/vue";
2
2
  import type { SpreadsheetToolData } from "../core/types";
3
3
  import "../engine/functions";
4
4
  type __VLS_Props = {
@@ -0,0 +1,4 @@
1
+ export declare const escapeHtml: (text: string) => string;
2
+ /** A link in a cell that should open in a new tab rather than navigate the
3
+ * host app away: http(s) on another origin. */
4
+ export declare function isCrossOriginHttpUrl(href: string, currentOrigin: string): boolean;
@@ -6,18 +6,20 @@
6
6
  */
7
7
  import "../style.css";
8
8
  import type { ToolPlugin } from "gui-chat-protocol/vue";
9
- import type { SpreadsheetToolData, SpreadsheetArgs } from "../core/types";
9
+ import type { SpreadsheetToolData, SpreadsheetArgs, SpreadsheetDispatchArgs } from "../core/types";
10
10
  import View from "./View.vue";
11
11
  import Preview from "./Preview.vue";
12
12
  /**
13
13
  * Spreadsheet plugin instance with Vue components
14
14
  */
15
- export declare const plugin: ToolPlugin<SpreadsheetToolData, never, SpreadsheetArgs>;
16
- export type { SpreadsheetCell, SpreadsheetSheet, SpreadsheetToolData, SpreadsheetArgs, } from "../core/types";
15
+ export declare const plugin: ToolPlugin<SpreadsheetToolData, never, SpreadsheetArgs | SpreadsheetDispatchArgs>;
16
+ export type { SpreadsheetCell, SpreadsheetCellValue, StorableCell, SpreadsheetSheet, SpreadsheetToolData, SpreadsheetArgs, SpreadsheetDispatchArgs, } from "../core/types";
17
17
  export { TOOL_NAME, TOOL_DEFINITION, SYSTEM_PROMPT, executeSpreadsheet, pluginCore, } from "../core/plugin";
18
+ export { executeSpreadsheetDispatch, isSpreadsheetDispatchArgs } from "../core/dispatch";
19
+ export { isSpreadsheetArtifactPath } from "../core/paths";
18
20
  export { samples } from "../core/samples";
19
21
  export { View, Preview };
20
22
  declare const _default: {
21
- plugin: ToolPlugin<SpreadsheetToolData, never, SpreadsheetArgs, import("gui-chat-protocol").InputHandler, Record<string, unknown>>;
23
+ plugin: ToolPlugin<SpreadsheetToolData, never, SpreadsheetArgs | SpreadsheetDispatchArgs, import("gui-chat-protocol").InputHandler, Record<string, unknown>>;
22
24
  };
23
25
  export default _default;
@@ -0,0 +1,24 @@
1
+ import type { SpreadsheetSheet } from "../core/types";
2
+ export interface SheetSessionHost {
3
+ loadFromHost(path: string): Promise<SpreadsheetSheet[]>;
4
+ saveToHost(path: string, sheets: SpreadsheetSheet[]): Promise<unknown>;
5
+ }
6
+ export type LoadOutcome = {
7
+ kind: "sheets";
8
+ sheets: SpreadsheetSheet[];
9
+ } | {
10
+ kind: "notSheets";
11
+ } | {
12
+ kind: "noHost";
13
+ path: string;
14
+ } | {
15
+ kind: "loadFailed";
16
+ error: unknown;
17
+ };
18
+ export interface SheetSession {
19
+ /** Resolves to null when a newer `load` started before this one finished. */
20
+ load(raw: unknown): Promise<LoadOutcome | null>;
21
+ /** Waits for every earlier save, so the last edit made is the last written. */
22
+ save(path: string, sheets: SpreadsheetSheet[]): Promise<void>;
23
+ }
24
+ export declare function createSheetSession(host: SheetSessionHost | null): SheetSession;