@pixerate/editor 0.223.1 → 0.223.2

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.
@@ -30,7 +30,7 @@ require('../chunk-4EFNS2HV.cjs');
30
30
 
31
31
 
32
32
 
33
- var _chunkBDT2IYXKcjs = require('../chunk-BDT2IYXK.cjs');
33
+ var _chunkVXLYEYP4cjs = require('../chunk-VXLYEYP4.cjs');
34
34
 
35
35
 
36
36
 
@@ -2281,4 +2281,4 @@ function createCanvasComponentToolProvider(controller, options = { id: "canvas-c
2281
2281
 
2282
2282
 
2283
2283
 
2284
- exports.AgentPlayback = _chunk3SLBXKAWcjs.AgentPlayback; exports.CAPTURE_EXCLUDE_SELECTOR = _chunkSVFEVKLFcjs.CAPTURE_EXCLUDE_SELECTOR; exports.CAPTURE_OVERLAY_SELECTORS = _chunkSVFEVKLFcjs.CAPTURE_OVERLAY_SELECTORS; exports.EDITOR_CANVAS_TOOLS = _chunkSVFEVKLFcjs.EDITOR_CANVAS_TOOLS; exports.EDITOR_MCP_TOOLS = _chunkSVFEVKLFcjs.EDITOR_MCP_TOOLS; exports.EDITOR_PROMPT_TOOLS = _chunkSVFEVKLFcjs.EDITOR_PROMPT_TOOLS; exports.EDIT_OPERATION_REQUIRED_ARGUMENTS = _chunkSVFEVKLFcjs.EDIT_OPERATION_REQUIRED_ARGUMENTS; exports.EXPORT_EXCLUDED_SELECTORS = _chunkSVFEVKLFcjs.EXPORT_EXCLUDED_SELECTORS; exports.JSON_RPC_ERRORS = _chunkZ7BVK32Ccjs.JSON_RPC_ERRORS; exports.MCP_PROTOCOL_VERSIONS = _chunkZ7BVK32Ccjs.MCP_PROTOCOL_VERSIONS; exports.MCP_TOOL_CANVAS_ADD_NODE = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_ADD_NODE; exports.MCP_TOOL_CANVAS_CONNECT = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_CONNECT; exports.MCP_TOOL_CANVAS_HIGHLIGHT = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_HIGHLIGHT; exports.MCP_TOOL_CANVAS_LAYOUT = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_LAYOUT; exports.MCP_TOOL_CANVAS_REMOVE_NODES = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_REMOVE_NODES; exports.MCP_TOOL_CANVAS_SELECT = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_SELECT; exports.MCP_TOOL_CANVAS_UPDATE_NODE = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_UPDATE_NODE; exports.MCP_TOOL_EDIT_CANVAS = _chunkSVFEVKLFcjs.MCP_TOOL_EDIT_CANVAS; exports.MCP_TOOL_EDIT_PROMPT = _chunkSVFEVKLFcjs.MCP_TOOL_EDIT_PROMPT; exports.MCP_TOOL_GET_EDITOR_STATE = _chunkSVFEVKLFcjs.MCP_TOOL_GET_EDITOR_STATE; exports.MCP_TOOL_PROMPT_INSERT_TEXT = _chunkSVFEVKLFcjs.MCP_TOOL_PROMPT_INSERT_TEXT; exports.MCP_TOOL_PROMPT_REPLACE_RANGE = _chunkSVFEVKLFcjs.MCP_TOOL_PROMPT_REPLACE_RANGE; exports.MCP_TOOL_PROMPT_SELECT = _chunkSVFEVKLFcjs.MCP_TOOL_PROMPT_SELECT; exports.MCP_TOOL_PROMPT_SET_VARIABLE = _chunkSVFEVKLFcjs.MCP_TOOL_PROMPT_SET_VARIABLE; exports.MCP_TOOL_VIEW_EDITOR = _chunkSVFEVKLFcjs.MCP_TOOL_VIEW_EDITOR; exports.PRESENCE_COLORS = _chunkK7ENM55Pcjs.PRESENCE_COLORS; exports.PerformanceQueue = _chunkK7ENM55Pcjs.PerformanceQueue; exports.PresenceStore = _chunkK7ENM55Pcjs.PresenceStore; exports.TOOL_BRIDGE_DEFAULT_PORT = _chunkZ7BVK32Ccjs.TOOL_BRIDGE_DEFAULT_PORT; exports.TOOL_BRIDGE_PROTOCOL = _chunkZ7BVK32Ccjs.TOOL_BRIDGE_PROTOCOL; exports.TOOL_DISCOVERY_TOOLS = _chunkZ7BVK32Ccjs.TOOL_DISCOVERY_TOOLS; exports.TOOL_NAMESPACE_SEPARATOR = _chunkZ7BVK32Ccjs.TOOL_NAMESPACE_SEPARATOR; exports.ToolRegistry = _chunkZ7BVK32Ccjs.ToolRegistry; exports.ToolRegistryError = _chunkZ7BVK32Ccjs.ToolRegistryError; exports.ToolRegistryView = _chunkZ7BVK32Ccjs.ToolRegistryView; exports.UNSAFE_JSON_KEYS = _chunk2BSBFBOAcjs.UNSAFE_JSON_KEYS; exports.baseToolName = _chunkZ7BVK32Ccjs.baseToolName; exports.callEvalTool = _chunkBDT2IYXKcjs.callEvalTool; exports.captureInputSchema = _chunkN2FGYZ4Qcjs.captureInputSchema; exports.captureOutputSize = _chunkN2FGYZ4Qcjs.captureOutputSize; exports.cloneElementWithStyles = _chunkSVFEVKLFcjs.cloneElementWithStyles; exports.connectToolBridge = _chunkZ7BVK32Ccjs.connectToolBridge; exports.createAgentPlayback = _chunk3SLBXKAWcjs.createAgentPlayback; exports.createCanvasComponentToolProvider = createCanvasComponentToolProvider; exports.createCaptureTool = _chunkN2FGYZ4Qcjs.createCaptureTool; exports.createCaptureToolProvider = _chunkN2FGYZ4Qcjs.createCaptureToolProvider; exports.createDocumentChartTools = createDocumentChartTools; exports.createEditorMcpToolProvider = createEditorMcpToolProvider; exports.createElementCaptureSurface = _chunkSVFEVKLFcjs.createElementCaptureSurface; exports.createInstantPresenceClock = _chunkK7ENM55Pcjs.createInstantPresenceClock; exports.createMcpServer = _chunkZ7BVK32Ccjs.createMcpServer; exports.createPlaybackToolProvider = _chunk3SLBXKAWcjs.createPlaybackToolProvider; exports.createRichTextToolProvider = createRichTextToolProvider; exports.createSpreadsheetToolProvider = createSpreadsheetToolProvider; exports.createStyleResolver = _chunkBDT2IYXKcjs.createStyleResolver; exports.createStyleToolProvider = _chunkBDT2IYXKcjs.createStyleToolProvider; exports.createToolRegistry = _chunkZ7BVK32Ccjs.createToolRegistry; exports.derivePlaybackSteps = _chunk3SLBXKAWcjs.derivePlaybackSteps; exports.elementToSvg = _chunkSVFEVKLFcjs.elementToSvg; exports.errorResult = _chunkZ7BVK32Ccjs.errorResult; exports.failedEvalChecks = _chunkBDT2IYXKcjs.failedEvalChecks; exports.imageEvalFixturePixels = _chunkBDT2IYXKcjs.imageEvalFixturePixels; exports.isUnsafeJsonKey = _chunk2BSBFBOAcjs.isUnsafeJsonKey; exports.listSkillEvals = _chunkBDT2IYXKcjs.listSkillEvals; exports.listSkills = _chunkZ7BVK32Ccjs.listSkills; exports.locatePlaybackChrome = _chunk3SLBXKAWcjs.locatePlaybackChrome; exports.matchesEvalExpectation = _chunkBDT2IYXKcjs.matchesEvalExpectation; exports.memoryStyleStore = _chunkBDT2IYXKcjs.memoryStyleStore; exports.normalizeSide = normalizeSide; exports.parseSkillFrontmatter = _chunkZ7BVK32Ccjs.parseSkillFrontmatter; exports.parseStyleDomains = _chunkBDT2IYXKcjs.parseStyleDomains; exports.planAgentCommand = planAgentCommand; exports.planCanvasPlaybackSteps = planCanvasPlaybackSteps; exports.planCanvasToolPlayback = planCanvasToolPlayback; exports.planToolCall = planToolCall; exports.playbackRectFromClient = _chunk3SLBXKAWcjs.playbackRectFromClient; exports.playbackRefsFromArgs = _chunk3SLBXKAWcjs.playbackRefsFromArgs; exports.playbackRefsFromResult = _chunk3SLBXKAWcjs.playbackRefsFromResult; exports.rasterizeElement = _chunkSVFEVKLFcjs.rasterizeElement; exports.readSkillFile = _chunkZ7BVK32Ccjs.readSkillFile; exports.readingTime = _chunk3SLBXKAWcjs.readingTime; exports.realPresenceClock = _chunkK7ENM55Pcjs.realPresenceClock; exports.renderStyle = _chunkBDT2IYXKcjs.renderStyle; exports.resolveCaptureRequest = _chunkN2FGYZ4Qcjs.resolveCaptureRequest; exports.runStyleChecks = _chunkBDT2IYXKcjs.runStyleChecks; exports.runToolScript = _chunkBDT2IYXKcjs.runToolScript; exports.searchTokens = _chunkZ7BVK32Ccjs.searchTokens; exports.searchTools = _chunkZ7BVK32Ccjs.searchTools; exports.stripPrototypeKeys = _chunk2BSBFBOAcjs.stripPrototypeKeys; exports.summarize = _chunkZ7BVK32Ccjs.summarize; exports.toCapturedImageData = _chunkN2FGYZ4Qcjs.toCapturedImageData; exports.toToolResult = _chunkZ7BVK32Ccjs.toToolResult; exports.toolCallToAgentCommand = toolCallToAgentCommand; exports.toolResultError = _chunkBDT2IYXKcjs.toolResultError; exports.toolResultValue = _chunkBDT2IYXKcjs.toolResultValue; exports.toolsetInstructions = _chunkZ7BVK32Ccjs.toolsetInstructions; exports.toolsetName = _chunkZ7BVK32Ccjs.toolsetName; exports.trackPresenceDrag = _chunkK7ENM55Pcjs.trackPresenceDrag; exports.tweenPresenceCursor = _chunkK7ENM55Pcjs.tweenPresenceCursor; exports.unionCaptureRects = _chunkN2FGYZ4Qcjs.unionCaptureRects; exports.unionRects = _chunk3SLBXKAWcjs.unionRects; exports.unknownToolMessage = _chunkZ7BVK32Ccjs.unknownToolMessage; exports.validateAgentCommand = validateAgentCommand; exports.validateJsonSchema = _chunkWOFXZ3XUcjs.validateJsonSchema;
2284
+ exports.AgentPlayback = _chunk3SLBXKAWcjs.AgentPlayback; exports.CAPTURE_EXCLUDE_SELECTOR = _chunkSVFEVKLFcjs.CAPTURE_EXCLUDE_SELECTOR; exports.CAPTURE_OVERLAY_SELECTORS = _chunkSVFEVKLFcjs.CAPTURE_OVERLAY_SELECTORS; exports.EDITOR_CANVAS_TOOLS = _chunkSVFEVKLFcjs.EDITOR_CANVAS_TOOLS; exports.EDITOR_MCP_TOOLS = _chunkSVFEVKLFcjs.EDITOR_MCP_TOOLS; exports.EDITOR_PROMPT_TOOLS = _chunkSVFEVKLFcjs.EDITOR_PROMPT_TOOLS; exports.EDIT_OPERATION_REQUIRED_ARGUMENTS = _chunkSVFEVKLFcjs.EDIT_OPERATION_REQUIRED_ARGUMENTS; exports.EXPORT_EXCLUDED_SELECTORS = _chunkSVFEVKLFcjs.EXPORT_EXCLUDED_SELECTORS; exports.JSON_RPC_ERRORS = _chunkZ7BVK32Ccjs.JSON_RPC_ERRORS; exports.MCP_PROTOCOL_VERSIONS = _chunkZ7BVK32Ccjs.MCP_PROTOCOL_VERSIONS; exports.MCP_TOOL_CANVAS_ADD_NODE = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_ADD_NODE; exports.MCP_TOOL_CANVAS_CONNECT = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_CONNECT; exports.MCP_TOOL_CANVAS_HIGHLIGHT = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_HIGHLIGHT; exports.MCP_TOOL_CANVAS_LAYOUT = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_LAYOUT; exports.MCP_TOOL_CANVAS_REMOVE_NODES = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_REMOVE_NODES; exports.MCP_TOOL_CANVAS_SELECT = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_SELECT; exports.MCP_TOOL_CANVAS_UPDATE_NODE = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_UPDATE_NODE; exports.MCP_TOOL_EDIT_CANVAS = _chunkSVFEVKLFcjs.MCP_TOOL_EDIT_CANVAS; exports.MCP_TOOL_EDIT_PROMPT = _chunkSVFEVKLFcjs.MCP_TOOL_EDIT_PROMPT; exports.MCP_TOOL_GET_EDITOR_STATE = _chunkSVFEVKLFcjs.MCP_TOOL_GET_EDITOR_STATE; exports.MCP_TOOL_PROMPT_INSERT_TEXT = _chunkSVFEVKLFcjs.MCP_TOOL_PROMPT_INSERT_TEXT; exports.MCP_TOOL_PROMPT_REPLACE_RANGE = _chunkSVFEVKLFcjs.MCP_TOOL_PROMPT_REPLACE_RANGE; exports.MCP_TOOL_PROMPT_SELECT = _chunkSVFEVKLFcjs.MCP_TOOL_PROMPT_SELECT; exports.MCP_TOOL_PROMPT_SET_VARIABLE = _chunkSVFEVKLFcjs.MCP_TOOL_PROMPT_SET_VARIABLE; exports.MCP_TOOL_VIEW_EDITOR = _chunkSVFEVKLFcjs.MCP_TOOL_VIEW_EDITOR; exports.PRESENCE_COLORS = _chunkK7ENM55Pcjs.PRESENCE_COLORS; exports.PerformanceQueue = _chunkK7ENM55Pcjs.PerformanceQueue; exports.PresenceStore = _chunkK7ENM55Pcjs.PresenceStore; exports.TOOL_BRIDGE_DEFAULT_PORT = _chunkZ7BVK32Ccjs.TOOL_BRIDGE_DEFAULT_PORT; exports.TOOL_BRIDGE_PROTOCOL = _chunkZ7BVK32Ccjs.TOOL_BRIDGE_PROTOCOL; exports.TOOL_DISCOVERY_TOOLS = _chunkZ7BVK32Ccjs.TOOL_DISCOVERY_TOOLS; exports.TOOL_NAMESPACE_SEPARATOR = _chunkZ7BVK32Ccjs.TOOL_NAMESPACE_SEPARATOR; exports.ToolRegistry = _chunkZ7BVK32Ccjs.ToolRegistry; exports.ToolRegistryError = _chunkZ7BVK32Ccjs.ToolRegistryError; exports.ToolRegistryView = _chunkZ7BVK32Ccjs.ToolRegistryView; exports.UNSAFE_JSON_KEYS = _chunk2BSBFBOAcjs.UNSAFE_JSON_KEYS; exports.baseToolName = _chunkZ7BVK32Ccjs.baseToolName; exports.callEvalTool = _chunkVXLYEYP4cjs.callEvalTool; exports.captureInputSchema = _chunkN2FGYZ4Qcjs.captureInputSchema; exports.captureOutputSize = _chunkN2FGYZ4Qcjs.captureOutputSize; exports.cloneElementWithStyles = _chunkSVFEVKLFcjs.cloneElementWithStyles; exports.connectToolBridge = _chunkZ7BVK32Ccjs.connectToolBridge; exports.createAgentPlayback = _chunk3SLBXKAWcjs.createAgentPlayback; exports.createCanvasComponentToolProvider = createCanvasComponentToolProvider; exports.createCaptureTool = _chunkN2FGYZ4Qcjs.createCaptureTool; exports.createCaptureToolProvider = _chunkN2FGYZ4Qcjs.createCaptureToolProvider; exports.createDocumentChartTools = createDocumentChartTools; exports.createEditorMcpToolProvider = createEditorMcpToolProvider; exports.createElementCaptureSurface = _chunkSVFEVKLFcjs.createElementCaptureSurface; exports.createInstantPresenceClock = _chunkK7ENM55Pcjs.createInstantPresenceClock; exports.createMcpServer = _chunkZ7BVK32Ccjs.createMcpServer; exports.createPlaybackToolProvider = _chunk3SLBXKAWcjs.createPlaybackToolProvider; exports.createRichTextToolProvider = createRichTextToolProvider; exports.createSpreadsheetToolProvider = createSpreadsheetToolProvider; exports.createStyleResolver = _chunkVXLYEYP4cjs.createStyleResolver; exports.createStyleToolProvider = _chunkVXLYEYP4cjs.createStyleToolProvider; exports.createToolRegistry = _chunkZ7BVK32Ccjs.createToolRegistry; exports.derivePlaybackSteps = _chunk3SLBXKAWcjs.derivePlaybackSteps; exports.elementToSvg = _chunkSVFEVKLFcjs.elementToSvg; exports.errorResult = _chunkZ7BVK32Ccjs.errorResult; exports.failedEvalChecks = _chunkVXLYEYP4cjs.failedEvalChecks; exports.imageEvalFixturePixels = _chunkVXLYEYP4cjs.imageEvalFixturePixels; exports.isUnsafeJsonKey = _chunk2BSBFBOAcjs.isUnsafeJsonKey; exports.listSkillEvals = _chunkVXLYEYP4cjs.listSkillEvals; exports.listSkills = _chunkZ7BVK32Ccjs.listSkills; exports.locatePlaybackChrome = _chunk3SLBXKAWcjs.locatePlaybackChrome; exports.matchesEvalExpectation = _chunkVXLYEYP4cjs.matchesEvalExpectation; exports.memoryStyleStore = _chunkVXLYEYP4cjs.memoryStyleStore; exports.normalizeSide = normalizeSide; exports.parseSkillFrontmatter = _chunkZ7BVK32Ccjs.parseSkillFrontmatter; exports.parseStyleDomains = _chunkVXLYEYP4cjs.parseStyleDomains; exports.planAgentCommand = planAgentCommand; exports.planCanvasPlaybackSteps = planCanvasPlaybackSteps; exports.planCanvasToolPlayback = planCanvasToolPlayback; exports.planToolCall = planToolCall; exports.playbackRectFromClient = _chunk3SLBXKAWcjs.playbackRectFromClient; exports.playbackRefsFromArgs = _chunk3SLBXKAWcjs.playbackRefsFromArgs; exports.playbackRefsFromResult = _chunk3SLBXKAWcjs.playbackRefsFromResult; exports.rasterizeElement = _chunkSVFEVKLFcjs.rasterizeElement; exports.readSkillFile = _chunkZ7BVK32Ccjs.readSkillFile; exports.readingTime = _chunk3SLBXKAWcjs.readingTime; exports.realPresenceClock = _chunkK7ENM55Pcjs.realPresenceClock; exports.renderStyle = _chunkVXLYEYP4cjs.renderStyle; exports.resolveCaptureRequest = _chunkN2FGYZ4Qcjs.resolveCaptureRequest; exports.runStyleChecks = _chunkVXLYEYP4cjs.runStyleChecks; exports.runToolScript = _chunkVXLYEYP4cjs.runToolScript; exports.searchTokens = _chunkZ7BVK32Ccjs.searchTokens; exports.searchTools = _chunkZ7BVK32Ccjs.searchTools; exports.stripPrototypeKeys = _chunk2BSBFBOAcjs.stripPrototypeKeys; exports.summarize = _chunkZ7BVK32Ccjs.summarize; exports.toCapturedImageData = _chunkN2FGYZ4Qcjs.toCapturedImageData; exports.toToolResult = _chunkZ7BVK32Ccjs.toToolResult; exports.toolCallToAgentCommand = toolCallToAgentCommand; exports.toolResultError = _chunkVXLYEYP4cjs.toolResultError; exports.toolResultValue = _chunkVXLYEYP4cjs.toolResultValue; exports.toolsetInstructions = _chunkZ7BVK32Ccjs.toolsetInstructions; exports.toolsetName = _chunkZ7BVK32Ccjs.toolsetName; exports.trackPresenceDrag = _chunkK7ENM55Pcjs.trackPresenceDrag; exports.tweenPresenceCursor = _chunkK7ENM55Pcjs.tweenPresenceCursor; exports.unionCaptureRects = _chunkN2FGYZ4Qcjs.unionCaptureRects; exports.unionRects = _chunk3SLBXKAWcjs.unionRects; exports.unknownToolMessage = _chunkZ7BVK32Ccjs.unknownToolMessage; exports.validateAgentCommand = validateAgentCommand; exports.validateJsonSchema = _chunkWOFXZ3XUcjs.validateJsonSchema;
@@ -30,7 +30,7 @@ import {
30
30
  runToolScript,
31
31
  toolResultError,
32
32
  toolResultValue
33
- } from "../chunk-SVHXD2VH.js";
33
+ } from "../chunk-H73MSHYZ.js";
34
34
  import {
35
35
  JSON_RPC_ERRORS,
36
36
  MCP_PROTOCOL_VERSIONS,
@@ -100,7 +100,11 @@ function matchesEvalExpectation(actual, expected) {
100
100
  return !!list && matches(list.map((row) => Array.isArray(row) ? row[index] : void 0), m);
101
101
  }
102
102
  case "$expr":
103
- return !!new Function("v", `return (${want});`)(actual);
103
+ try {
104
+ return !!new Function("v", `return (${want});`)(actual);
105
+ } catch {
106
+ return false;
107
+ }
104
108
  case "$nonEmpty":
105
109
  return !!list && matches(list.filter((a) => !isEmpty(a)), want);
106
110
  default:
@@ -100,7 +100,11 @@ function matchesEvalExpectation(actual, expected) {
100
100
  return !!list && matches(list.map((row) => Array.isArray(row) ? row[index] : void 0), m);
101
101
  }
102
102
  case "$expr":
103
- return !!new Function("v", `return (${want});`)(actual);
103
+ try {
104
+ return !!new Function("v", `return (${want});`)(actual);
105
+ } catch (e2) {
106
+ return false;
107
+ }
104
108
  case "$nonEmpty":
105
109
  return !!list && matches(list.filter((a) => !isEmpty(a)), want);
106
110
  default:
@@ -126,7 +130,7 @@ function toolResultValue(result) {
126
130
  if (!_optionalChain([text, 'optionalAccess', _4 => _4.text])) return void 0;
127
131
  try {
128
132
  return JSON.parse(text.text);
129
- } catch (e2) {
133
+ } catch (e3) {
130
134
  return text.text;
131
135
  }
132
136
  }
@@ -13,7 +13,7 @@
13
13
 
14
14
 
15
15
 
16
- var _chunkBDT2IYXKcjs = require('../chunk-BDT2IYXK.cjs');
16
+ var _chunkVXLYEYP4cjs = require('../chunk-VXLYEYP4.cjs');
17
17
 
18
18
 
19
19
 
@@ -199,4 +199,4 @@ var _chunk2BSBFBOAcjs = require('../chunk-2BSBFBOA.cjs');
199
199
 
200
200
 
201
201
 
202
- exports.AgentPlayback = _chunk3SLBXKAWcjs.AgentPlayback; exports.CAPTURE_EXCLUDE_SELECTOR = _chunkSVFEVKLFcjs.CAPTURE_EXCLUDE_SELECTOR; exports.CAPTURE_OVERLAY_SELECTORS = _chunkSVFEVKLFcjs.CAPTURE_OVERLAY_SELECTORS; exports.EDITOR_CANVAS_TOOLS = _chunkSVFEVKLFcjs.EDITOR_CANVAS_TOOLS; exports.EDITOR_MCP_TOOLS = _chunkSVFEVKLFcjs.EDITOR_MCP_TOOLS; exports.EDITOR_PROMPT_TOOLS = _chunkSVFEVKLFcjs.EDITOR_PROMPT_TOOLS; exports.EDIT_OPERATION_REQUIRED_ARGUMENTS = _chunkSVFEVKLFcjs.EDIT_OPERATION_REQUIRED_ARGUMENTS; exports.EXPORT_EXCLUDED_SELECTORS = _chunkSVFEVKLFcjs.EXPORT_EXCLUDED_SELECTORS; exports.JSON_RPC_ERRORS = _chunkZ7BVK32Ccjs.JSON_RPC_ERRORS; exports.MCP_PROTOCOL_VERSIONS = _chunkZ7BVK32Ccjs.MCP_PROTOCOL_VERSIONS; exports.MCP_TOOL_CANVAS_ADD_NODE = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_ADD_NODE; exports.MCP_TOOL_CANVAS_CONNECT = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_CONNECT; exports.MCP_TOOL_CANVAS_HIGHLIGHT = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_HIGHLIGHT; exports.MCP_TOOL_CANVAS_LAYOUT = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_LAYOUT; exports.MCP_TOOL_CANVAS_REMOVE_NODES = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_REMOVE_NODES; exports.MCP_TOOL_CANVAS_SELECT = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_SELECT; exports.MCP_TOOL_CANVAS_UPDATE_NODE = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_UPDATE_NODE; exports.MCP_TOOL_EDIT_CANVAS = _chunkSVFEVKLFcjs.MCP_TOOL_EDIT_CANVAS; exports.MCP_TOOL_EDIT_PROMPT = _chunkSVFEVKLFcjs.MCP_TOOL_EDIT_PROMPT; exports.MCP_TOOL_GET_EDITOR_STATE = _chunkSVFEVKLFcjs.MCP_TOOL_GET_EDITOR_STATE; exports.MCP_TOOL_PROMPT_INSERT_TEXT = _chunkSVFEVKLFcjs.MCP_TOOL_PROMPT_INSERT_TEXT; exports.MCP_TOOL_PROMPT_REPLACE_RANGE = _chunkSVFEVKLFcjs.MCP_TOOL_PROMPT_REPLACE_RANGE; exports.MCP_TOOL_PROMPT_SELECT = _chunkSVFEVKLFcjs.MCP_TOOL_PROMPT_SELECT; exports.MCP_TOOL_PROMPT_SET_VARIABLE = _chunkSVFEVKLFcjs.MCP_TOOL_PROMPT_SET_VARIABLE; exports.MCP_TOOL_VIEW_EDITOR = _chunkSVFEVKLFcjs.MCP_TOOL_VIEW_EDITOR; exports.PRESENCE_COLORS = _chunkK7ENM55Pcjs.PRESENCE_COLORS; exports.PerformanceQueue = _chunkK7ENM55Pcjs.PerformanceQueue; exports.PresenceStore = _chunkK7ENM55Pcjs.PresenceStore; exports.TOOL_BRIDGE_DEFAULT_PORT = _chunkZ7BVK32Ccjs.TOOL_BRIDGE_DEFAULT_PORT; exports.TOOL_BRIDGE_PROTOCOL = _chunkZ7BVK32Ccjs.TOOL_BRIDGE_PROTOCOL; exports.TOOL_DISCOVERY_TOOLS = _chunkZ7BVK32Ccjs.TOOL_DISCOVERY_TOOLS; exports.TOOL_NAMESPACE_SEPARATOR = _chunkZ7BVK32Ccjs.TOOL_NAMESPACE_SEPARATOR; exports.ToolRegistry = _chunkZ7BVK32Ccjs.ToolRegistry; exports.ToolRegistryError = _chunkZ7BVK32Ccjs.ToolRegistryError; exports.ToolRegistryView = _chunkZ7BVK32Ccjs.ToolRegistryView; exports.UNSAFE_JSON_KEYS = _chunk2BSBFBOAcjs.UNSAFE_JSON_KEYS; exports.baseToolName = _chunkZ7BVK32Ccjs.baseToolName; exports.callEvalTool = _chunkBDT2IYXKcjs.callEvalTool; exports.captureInputSchema = _chunkN2FGYZ4Qcjs.captureInputSchema; exports.captureOutputSize = _chunkN2FGYZ4Qcjs.captureOutputSize; exports.cloneElementWithStyles = _chunkSVFEVKLFcjs.cloneElementWithStyles; exports.connectToolBridge = _chunkZ7BVK32Ccjs.connectToolBridge; exports.createAgentPlayback = _chunk3SLBXKAWcjs.createAgentPlayback; exports.createCaptureTool = _chunkN2FGYZ4Qcjs.createCaptureTool; exports.createCaptureToolProvider = _chunkN2FGYZ4Qcjs.createCaptureToolProvider; exports.createElementCaptureSurface = _chunkSVFEVKLFcjs.createElementCaptureSurface; exports.createInstantPresenceClock = _chunkK7ENM55Pcjs.createInstantPresenceClock; exports.createMcpServer = _chunkZ7BVK32Ccjs.createMcpServer; exports.createPlaybackToolProvider = _chunk3SLBXKAWcjs.createPlaybackToolProvider; exports.createStyleResolver = _chunkBDT2IYXKcjs.createStyleResolver; exports.createStyleToolProvider = _chunkBDT2IYXKcjs.createStyleToolProvider; exports.createToolRegistry = _chunkZ7BVK32Ccjs.createToolRegistry; exports.derivePlaybackSteps = _chunk3SLBXKAWcjs.derivePlaybackSteps; exports.elementToSvg = _chunkSVFEVKLFcjs.elementToSvg; exports.errorResult = _chunkZ7BVK32Ccjs.errorResult; exports.failedEvalChecks = _chunkBDT2IYXKcjs.failedEvalChecks; exports.imageEvalFixturePixels = _chunkBDT2IYXKcjs.imageEvalFixturePixels; exports.isUnsafeJsonKey = _chunk2BSBFBOAcjs.isUnsafeJsonKey; exports.listSkillEvals = _chunkBDT2IYXKcjs.listSkillEvals; exports.listSkills = _chunkZ7BVK32Ccjs.listSkills; exports.locatePlaybackChrome = _chunk3SLBXKAWcjs.locatePlaybackChrome; exports.matchesEvalExpectation = _chunkBDT2IYXKcjs.matchesEvalExpectation; exports.memoryStyleStore = _chunkBDT2IYXKcjs.memoryStyleStore; exports.parseSkillFrontmatter = _chunkZ7BVK32Ccjs.parseSkillFrontmatter; exports.parseStyleDomains = _chunkBDT2IYXKcjs.parseStyleDomains; exports.playbackRectFromClient = _chunk3SLBXKAWcjs.playbackRectFromClient; exports.playbackRefsFromArgs = _chunk3SLBXKAWcjs.playbackRefsFromArgs; exports.playbackRefsFromResult = _chunk3SLBXKAWcjs.playbackRefsFromResult; exports.rasterizeElement = _chunkSVFEVKLFcjs.rasterizeElement; exports.readSkillFile = _chunkZ7BVK32Ccjs.readSkillFile; exports.readingTime = _chunk3SLBXKAWcjs.readingTime; exports.realPresenceClock = _chunkK7ENM55Pcjs.realPresenceClock; exports.renderStyle = _chunkBDT2IYXKcjs.renderStyle; exports.resolveCaptureRequest = _chunkN2FGYZ4Qcjs.resolveCaptureRequest; exports.runStyleChecks = _chunkBDT2IYXKcjs.runStyleChecks; exports.runToolScript = _chunkBDT2IYXKcjs.runToolScript; exports.searchTokens = _chunkZ7BVK32Ccjs.searchTokens; exports.searchTools = _chunkZ7BVK32Ccjs.searchTools; exports.stripPrototypeKeys = _chunk2BSBFBOAcjs.stripPrototypeKeys; exports.summarize = _chunkZ7BVK32Ccjs.summarize; exports.toCapturedImageData = _chunkN2FGYZ4Qcjs.toCapturedImageData; exports.toToolResult = _chunkZ7BVK32Ccjs.toToolResult; exports.toolResultError = _chunkBDT2IYXKcjs.toolResultError; exports.toolResultValue = _chunkBDT2IYXKcjs.toolResultValue; exports.toolsetInstructions = _chunkZ7BVK32Ccjs.toolsetInstructions; exports.toolsetName = _chunkZ7BVK32Ccjs.toolsetName; exports.trackPresenceDrag = _chunkK7ENM55Pcjs.trackPresenceDrag; exports.tweenPresenceCursor = _chunkK7ENM55Pcjs.tweenPresenceCursor; exports.unionCaptureRects = _chunkN2FGYZ4Qcjs.unionCaptureRects; exports.unionRects = _chunk3SLBXKAWcjs.unionRects; exports.unknownToolMessage = _chunkZ7BVK32Ccjs.unknownToolMessage; exports.validateJsonSchema = _chunkWOFXZ3XUcjs.validateJsonSchema;
202
+ exports.AgentPlayback = _chunk3SLBXKAWcjs.AgentPlayback; exports.CAPTURE_EXCLUDE_SELECTOR = _chunkSVFEVKLFcjs.CAPTURE_EXCLUDE_SELECTOR; exports.CAPTURE_OVERLAY_SELECTORS = _chunkSVFEVKLFcjs.CAPTURE_OVERLAY_SELECTORS; exports.EDITOR_CANVAS_TOOLS = _chunkSVFEVKLFcjs.EDITOR_CANVAS_TOOLS; exports.EDITOR_MCP_TOOLS = _chunkSVFEVKLFcjs.EDITOR_MCP_TOOLS; exports.EDITOR_PROMPT_TOOLS = _chunkSVFEVKLFcjs.EDITOR_PROMPT_TOOLS; exports.EDIT_OPERATION_REQUIRED_ARGUMENTS = _chunkSVFEVKLFcjs.EDIT_OPERATION_REQUIRED_ARGUMENTS; exports.EXPORT_EXCLUDED_SELECTORS = _chunkSVFEVKLFcjs.EXPORT_EXCLUDED_SELECTORS; exports.JSON_RPC_ERRORS = _chunkZ7BVK32Ccjs.JSON_RPC_ERRORS; exports.MCP_PROTOCOL_VERSIONS = _chunkZ7BVK32Ccjs.MCP_PROTOCOL_VERSIONS; exports.MCP_TOOL_CANVAS_ADD_NODE = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_ADD_NODE; exports.MCP_TOOL_CANVAS_CONNECT = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_CONNECT; exports.MCP_TOOL_CANVAS_HIGHLIGHT = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_HIGHLIGHT; exports.MCP_TOOL_CANVAS_LAYOUT = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_LAYOUT; exports.MCP_TOOL_CANVAS_REMOVE_NODES = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_REMOVE_NODES; exports.MCP_TOOL_CANVAS_SELECT = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_SELECT; exports.MCP_TOOL_CANVAS_UPDATE_NODE = _chunkSVFEVKLFcjs.MCP_TOOL_CANVAS_UPDATE_NODE; exports.MCP_TOOL_EDIT_CANVAS = _chunkSVFEVKLFcjs.MCP_TOOL_EDIT_CANVAS; exports.MCP_TOOL_EDIT_PROMPT = _chunkSVFEVKLFcjs.MCP_TOOL_EDIT_PROMPT; exports.MCP_TOOL_GET_EDITOR_STATE = _chunkSVFEVKLFcjs.MCP_TOOL_GET_EDITOR_STATE; exports.MCP_TOOL_PROMPT_INSERT_TEXT = _chunkSVFEVKLFcjs.MCP_TOOL_PROMPT_INSERT_TEXT; exports.MCP_TOOL_PROMPT_REPLACE_RANGE = _chunkSVFEVKLFcjs.MCP_TOOL_PROMPT_REPLACE_RANGE; exports.MCP_TOOL_PROMPT_SELECT = _chunkSVFEVKLFcjs.MCP_TOOL_PROMPT_SELECT; exports.MCP_TOOL_PROMPT_SET_VARIABLE = _chunkSVFEVKLFcjs.MCP_TOOL_PROMPT_SET_VARIABLE; exports.MCP_TOOL_VIEW_EDITOR = _chunkSVFEVKLFcjs.MCP_TOOL_VIEW_EDITOR; exports.PRESENCE_COLORS = _chunkK7ENM55Pcjs.PRESENCE_COLORS; exports.PerformanceQueue = _chunkK7ENM55Pcjs.PerformanceQueue; exports.PresenceStore = _chunkK7ENM55Pcjs.PresenceStore; exports.TOOL_BRIDGE_DEFAULT_PORT = _chunkZ7BVK32Ccjs.TOOL_BRIDGE_DEFAULT_PORT; exports.TOOL_BRIDGE_PROTOCOL = _chunkZ7BVK32Ccjs.TOOL_BRIDGE_PROTOCOL; exports.TOOL_DISCOVERY_TOOLS = _chunkZ7BVK32Ccjs.TOOL_DISCOVERY_TOOLS; exports.TOOL_NAMESPACE_SEPARATOR = _chunkZ7BVK32Ccjs.TOOL_NAMESPACE_SEPARATOR; exports.ToolRegistry = _chunkZ7BVK32Ccjs.ToolRegistry; exports.ToolRegistryError = _chunkZ7BVK32Ccjs.ToolRegistryError; exports.ToolRegistryView = _chunkZ7BVK32Ccjs.ToolRegistryView; exports.UNSAFE_JSON_KEYS = _chunk2BSBFBOAcjs.UNSAFE_JSON_KEYS; exports.baseToolName = _chunkZ7BVK32Ccjs.baseToolName; exports.callEvalTool = _chunkVXLYEYP4cjs.callEvalTool; exports.captureInputSchema = _chunkN2FGYZ4Qcjs.captureInputSchema; exports.captureOutputSize = _chunkN2FGYZ4Qcjs.captureOutputSize; exports.cloneElementWithStyles = _chunkSVFEVKLFcjs.cloneElementWithStyles; exports.connectToolBridge = _chunkZ7BVK32Ccjs.connectToolBridge; exports.createAgentPlayback = _chunk3SLBXKAWcjs.createAgentPlayback; exports.createCaptureTool = _chunkN2FGYZ4Qcjs.createCaptureTool; exports.createCaptureToolProvider = _chunkN2FGYZ4Qcjs.createCaptureToolProvider; exports.createElementCaptureSurface = _chunkSVFEVKLFcjs.createElementCaptureSurface; exports.createInstantPresenceClock = _chunkK7ENM55Pcjs.createInstantPresenceClock; exports.createMcpServer = _chunkZ7BVK32Ccjs.createMcpServer; exports.createPlaybackToolProvider = _chunk3SLBXKAWcjs.createPlaybackToolProvider; exports.createStyleResolver = _chunkVXLYEYP4cjs.createStyleResolver; exports.createStyleToolProvider = _chunkVXLYEYP4cjs.createStyleToolProvider; exports.createToolRegistry = _chunkZ7BVK32Ccjs.createToolRegistry; exports.derivePlaybackSteps = _chunk3SLBXKAWcjs.derivePlaybackSteps; exports.elementToSvg = _chunkSVFEVKLFcjs.elementToSvg; exports.errorResult = _chunkZ7BVK32Ccjs.errorResult; exports.failedEvalChecks = _chunkVXLYEYP4cjs.failedEvalChecks; exports.imageEvalFixturePixels = _chunkVXLYEYP4cjs.imageEvalFixturePixels; exports.isUnsafeJsonKey = _chunk2BSBFBOAcjs.isUnsafeJsonKey; exports.listSkillEvals = _chunkVXLYEYP4cjs.listSkillEvals; exports.listSkills = _chunkZ7BVK32Ccjs.listSkills; exports.locatePlaybackChrome = _chunk3SLBXKAWcjs.locatePlaybackChrome; exports.matchesEvalExpectation = _chunkVXLYEYP4cjs.matchesEvalExpectation; exports.memoryStyleStore = _chunkVXLYEYP4cjs.memoryStyleStore; exports.parseSkillFrontmatter = _chunkZ7BVK32Ccjs.parseSkillFrontmatter; exports.parseStyleDomains = _chunkVXLYEYP4cjs.parseStyleDomains; exports.playbackRectFromClient = _chunk3SLBXKAWcjs.playbackRectFromClient; exports.playbackRefsFromArgs = _chunk3SLBXKAWcjs.playbackRefsFromArgs; exports.playbackRefsFromResult = _chunk3SLBXKAWcjs.playbackRefsFromResult; exports.rasterizeElement = _chunkSVFEVKLFcjs.rasterizeElement; exports.readSkillFile = _chunkZ7BVK32Ccjs.readSkillFile; exports.readingTime = _chunk3SLBXKAWcjs.readingTime; exports.realPresenceClock = _chunkK7ENM55Pcjs.realPresenceClock; exports.renderStyle = _chunkVXLYEYP4cjs.renderStyle; exports.resolveCaptureRequest = _chunkN2FGYZ4Qcjs.resolveCaptureRequest; exports.runStyleChecks = _chunkVXLYEYP4cjs.runStyleChecks; exports.runToolScript = _chunkVXLYEYP4cjs.runToolScript; exports.searchTokens = _chunkZ7BVK32Ccjs.searchTokens; exports.searchTools = _chunkZ7BVK32Ccjs.searchTools; exports.stripPrototypeKeys = _chunk2BSBFBOAcjs.stripPrototypeKeys; exports.summarize = _chunkZ7BVK32Ccjs.summarize; exports.toCapturedImageData = _chunkN2FGYZ4Qcjs.toCapturedImageData; exports.toToolResult = _chunkZ7BVK32Ccjs.toToolResult; exports.toolResultError = _chunkVXLYEYP4cjs.toolResultError; exports.toolResultValue = _chunkVXLYEYP4cjs.toolResultValue; exports.toolsetInstructions = _chunkZ7BVK32Ccjs.toolsetInstructions; exports.toolsetName = _chunkZ7BVK32Ccjs.toolsetName; exports.trackPresenceDrag = _chunkK7ENM55Pcjs.trackPresenceDrag; exports.tweenPresenceCursor = _chunkK7ENM55Pcjs.tweenPresenceCursor; exports.unionCaptureRects = _chunkN2FGYZ4Qcjs.unionCaptureRects; exports.unionRects = _chunk3SLBXKAWcjs.unionRects; exports.unknownToolMessage = _chunkZ7BVK32Ccjs.unknownToolMessage; exports.validateJsonSchema = _chunkWOFXZ3XUcjs.validateJsonSchema;
@@ -13,7 +13,7 @@ import {
13
13
  runToolScript,
14
14
  toolResultError,
15
15
  toolResultValue
16
- } from "../chunk-SVHXD2VH.js";
16
+ } from "../chunk-H73MSHYZ.js";
17
17
  import {
18
18
  JSON_RPC_ERRORS,
19
19
  MCP_PROTOCOL_VERSIONS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor",
3
- "version": "0.223.1",
3
+ "version": "0.223.2",
4
4
  "description": "UI-agnostic rich text and media editing core library",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
package/skills/README.md CHANGED
@@ -55,6 +55,8 @@ request like "clean up this export" finds it.
55
55
  | [`pixerate-video-montage`](pixerate-video-montage/SKILL.md) | Video (`pixerate-video-editor`) | Selects on the beat, varied pacing, short transitions with handles, music under everything and a clean ending. |
56
56
  | [`pixerate-chart-choose-and-build`](pixerate-chart-choose-and-build/SKILL.md) | Chart (`pixerate-chart`) | From the question to the type, clean ordered data, honest axes, units and colour-blind-safe colours. |
57
57
  | [`pixerate-chart-annotate`](pixerate-chart-annotate/SKILL.md) | Chart (`pixerate-chart`) | A takeaway title, highlights, labelled targets and ranges, callouts and a source note, kept few. |
58
+ | [`pixerate-spreadsheet-analysis`](pixerate-spreadsheet-analysis/SKILL.md) | Spreadsheet (`pixerate-spreadsheet`) | Summary tables with SUMIFS, COUNTIFS and AVERAGEIFS, growth, shares and rankings as live formulas that reconcile with the data. |
59
+ | [`pixerate-spreadsheet-financial-model`](pixerate-spreadsheet-financial-model/SKILL.md) | Spreadsheet (`pixerate-spreadsheet`) | Inputs apart from calculations, one formula pattern per row, no hard-coded numbers, scenarios and audits. |
58
60
  | [`pixerate-talking-head-edit`](pixerate-talking-head-edit/SKILL.md) | Video (`pixerate-video-editor`) | Talking-head cleanup by transcript: fillers, long pauses and stutters found in one pass and cut as one plan reviewed before it applies. Owns the dialogue tools. |
59
61
  | [`pixerate-bgm-alignment`](pixerate-bgm-alignment/SKILL.md) | Video (`pixerate-video-editor`) | Background music: beats and cuts on the beat, ducking under speech, loudness in LUFS. Owns the music tools. |
60
62
 
@@ -21,6 +21,8 @@ For these jobs, load the workflow skill too: it has the method, the checks betwe
21
21
  the pitfalls, and uses the tools below.
22
22
 
23
23
  - `pixerate-spreadsheet-clean-import`: tidying imported or pasted data: structure, headers, types, dates, duplicates.
24
+ - `pixerate-spreadsheet-analysis`: summaries, growth, shares and rankings as formulas that reconcile.
25
+ - `pixerate-spreadsheet-financial-model`: inputs, consistent calculation rows, scenarios and audits.
24
26
 
25
27
  ## Working method
26
28
 
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: pixerate-spreadsheet-analysis
3
+ description: The analysis workflow in a Pixerate spreadsheet - turning tidy data into answers with live formulas - summary tables by category (SUMIFS, COUNTIFS, AVERAGEIFS), distinct lists with UNIQUE, ranked and filtered views with SORT and FILTER, period totals and growth, shares of a total, checks that the summary reconciles with the data, and handing the result to a chart. Use when asked to summarise, total, count, average, group, pivot, rank, compare, break down or analyse data in a spreadsheet, work out growth or percentages, find the top items, or prepare numbers for a chart, or when get_range and set_range are available. Load with pixerate-spreadsheet, which lists every tool.
4
+ metadata:
5
+ kind: workflow
6
+ reference: pixerate-spreadsheet
7
+ ---
8
+
9
+ # Analysing data in a spreadsheet
10
+
11
+ An analysis is a set of formulas that answer a question from the data and keep answering it
12
+ when the data changes. Start from tidy data (one header row, one record per row; clean it first
13
+ with `pixerate-spreadsheet-clean-import` if needed), put each summary in its own block beside or
14
+ below the data, and make every number a formula over the data, never a typed result.
15
+
16
+ This skill is the method. `pixerate-spreadsheet` has the full tool reference and the supported
17
+ functions; for the chart that shows the result, see `pixerate-chart-choose-and-build`.
18
+
19
+ ## Goal and quality bar
20
+
21
+ - Every result is a formula over the data (`SUMIFS`, `COUNTIFS`, `AVERAGEIFS`, `FILTER`,
22
+ `UNIQUE`, `SORT`), with absolute references to the data range, so it updates and can be
23
+ checked.
24
+ - Summaries have a header row and one row per group; nothing is typed over a formula.
25
+ - The summary reconciles: group totals add up to the data's total.
26
+ - No formula errors (`get_range` `errors` empty); expected errors handled explicitly
27
+ (`IFERROR` around a division by a possible zero), and blanks where a value does not apply.
28
+ - The data itself is untouched.
29
+
30
+ ## Inputs to confirm
31
+
32
+ - **The question** (totals by region, growth by month, top products) and the measure.
33
+ - **Where the result goes** (a cell such as G1, or below the data).
34
+ - **Groups:** a fixed list the user gives, or every distinct value in the data (`UNIQUE`).
35
+
36
+ ## Working method
37
+
38
+ 1. **Read the data.** `get_range` the header and a sample; note the data range, which column is
39
+ the group and which the measure, and whether it is tidy.
40
+ **Check:** one header row, one record per row, numbers as numbers.
41
+ 2. **Lay out the summary** in an empty area: a header row, then one row per group. List groups
42
+ yourself when there are few and fixed, or `=UNIQUE(B2:B500)` for every value (leave room for
43
+ it to spill).
44
+ 3. **Write the formulas in one block.** `set_range` with the header and a formula per row, each
45
+ with absolute data references and a relative group cell: `=SUMIFS($D$2:$D$500,$B$2:$B$500,G2)`.
46
+ **Check:** the returned `errors` is empty.
47
+ 4. **Reconcile.** Add a total row (`=SUM(H2:H5)`) and compare it with `=SUM($D$2:$D$500)`; for
48
+ counts, with `=COUNTA` of the key column.
49
+ **Check:** they match; if not, a group is missing or a value is text.
50
+ 5. **Derived measures**: growth `=B3/B2-1` (blank for the first period), share `=H2/SUM($H$2:$H$5)`,
51
+ rank `=RANK(H2,$H$2:$H$5)`, ranked lists `=SORT(G2:H5,2,-1)`.
52
+ **Check:** `get_range` values match a hand calculation for one row.
53
+ 6. **Hand over.** Tell the user where the summary is and what each column means; to chart it,
54
+ pass the summary block (categories and values) to the chart editor's `set_chart_data`.
55
+
56
+ ## Decision table
57
+
58
+ | Question | Formula |
59
+ | --- | --- |
60
+ | Total per group | `SUMIFS(measure, group_range, group)` |
61
+ | How many per group | `COUNTIFS(group_range, group)` |
62
+ | Average per group | `AVERAGEIFS(measure, group_range, group)` |
63
+ | Two conditions (region and month) | `SUMIFS` with two criteria pairs |
64
+ | Every distinct group | `UNIQUE(group_range)`, spilled |
65
+ | Rows that meet a condition | `FILTER(data, condition)` |
66
+ | Top items | `SORT` (or `SORTBY`) the summary by its total, descending |
67
+ | Change from the previous period | `=this/previous-1`, first period blank |
68
+ | Share of total | `=value/SUM($total_range)` |
69
+
70
+ ## Recipes
71
+
72
+ **Totals by region at G1.** `set_range` from G1 with `[["Region", "Total"], ["North",
73
+ "=SUMIFS($D$2:$D$11,$B$2:$B$11,G2)"], ...]` (each row's formula pointing at its own G cell).
74
+
75
+ **Month-over-month growth in C.** `set_range` from C1: `[["Growth"], [null], ["=B3/B2-1"],
76
+ ["=B4/B3-1"], ...]`.
77
+
78
+ **Orders and average per product.** Columns `=COUNTIFS($C$2:$C$11,G2)` and
79
+ `=AVERAGEIFS($D$2:$D$11,$C$2:$C$11,G2)`.
80
+
81
+ ## Pitfalls
82
+
83
+ - **Typed results.** A number typed into a summary does not update; always a formula.
84
+ - **Relative data references.** `D2:D11` shifts when you write the formula down; use `$D$2:$D$11`.
85
+ - **Text numbers** in the measure column are skipped by `SUMIFS`; clean the data first.
86
+ - **Spills blocked**: `UNIQUE`, `FILTER` and `SORT` need empty cells below and to the right.
87
+ - **Totals inside the data range** double-count; keep summaries outside it.
88
+ - **0 for "no previous period"** misleads averages; leave it blank.
89
+ - **`set_range` writes formulas as given**: write each row's own references.
90
+
91
+ ## Tools used
92
+
93
+ - `get_range`: read the data and check every result and error
94
+ - `set_range`: write summary blocks with headers and formulas, one undo step
95
+ - `set_cell`: fix one formula
96
+ - `insert_columns`, `insert_rows`: make room for a summary
97
+ - `undo_spreadsheet`: take back a step
@@ -0,0 +1,148 @@
1
+ {
2
+ "prompt": "For each product, show how many orders there were and the average order amount, in a small table starting at G1 (Product, Orders, Average), with formulas.",
3
+ "fixture": {
4
+ "editor": "spreadsheet",
5
+ "rows": [
6
+ [
7
+ "Date",
8
+ "Region",
9
+ "Product",
10
+ "Amount"
11
+ ],
12
+ [
13
+ "2026-01-05",
14
+ "North",
15
+ "Widget",
16
+ 120
17
+ ],
18
+ [
19
+ "2026-01-07",
20
+ "South",
21
+ "Gadget",
22
+ 80
23
+ ],
24
+ [
25
+ "2026-01-12",
26
+ "North",
27
+ "Gadget",
28
+ 95
29
+ ],
30
+ [
31
+ "2026-01-15",
32
+ "East",
33
+ "Widget",
34
+ 60
35
+ ],
36
+ [
37
+ "2026-01-20",
38
+ "West",
39
+ "Widget",
40
+ 150
41
+ ],
42
+ [
43
+ "2026-02-02",
44
+ "South",
45
+ "Widget",
46
+ 110
47
+ ],
48
+ [
49
+ "2026-02-08",
50
+ "East",
51
+ "Gadget",
52
+ 70
53
+ ],
54
+ [
55
+ "2026-02-11",
56
+ "North",
57
+ "Widget",
58
+ 130
59
+ ],
60
+ [
61
+ "2026-02-19",
62
+ "West",
63
+ "Gadget",
64
+ 90
65
+ ],
66
+ [
67
+ "2026-02-25",
68
+ "South",
69
+ "Gadget",
70
+ 85
71
+ ]
72
+ ]
73
+ },
74
+ "checks": [
75
+ {
76
+ "why": "Widget: 5 orders averaging 114; Gadget: 5 orders averaging 84",
77
+ "tool": "get_range",
78
+ "args": {
79
+ "range": "G2:I10"
80
+ },
81
+ "expect": {
82
+ "values": {
83
+ "$nonEmpty": {
84
+ "$length": 2,
85
+ "$expr": "v.some((r) => r[0] === 'Widget' && r[1] === 5 && Math.abs(r[2] - 114) < 0.01) && v.some((r) => r[0] === 'Gadget' && r[1] === 5 && Math.abs(r[2] - 84) < 0.01)"
86
+ }
87
+ }
88
+ }
89
+ },
90
+ {
91
+ "why": "formulas for the counts and averages",
92
+ "tool": "get_range",
93
+ "args": {
94
+ "range": "H2:I3"
95
+ },
96
+ "expect": {
97
+ "raw": {
98
+ "$every": {
99
+ "$every": {
100
+ "$match": "^="
101
+ }
102
+ }
103
+ }
104
+ }
105
+ },
106
+ {
107
+ "why": "headers",
108
+ "tool": "get_range",
109
+ "args": {
110
+ "range": "G1:I1"
111
+ },
112
+ "expect": {
113
+ "values": [
114
+ {
115
+ "$every": {
116
+ "$match": "\\S"
117
+ }
118
+ }
119
+ ]
120
+ }
121
+ }
122
+ ],
123
+ "solution": [
124
+ {
125
+ "tool": "set_range",
126
+ "args": {
127
+ "start": "G1",
128
+ "values": [
129
+ [
130
+ "Product",
131
+ "Orders",
132
+ "Average"
133
+ ],
134
+ [
135
+ "Widget",
136
+ "=COUNTIFS($C$2:$C$11,G2)",
137
+ "=AVERAGEIFS($D$2:$D$11,$C$2:$C$11,G2)"
138
+ ],
139
+ [
140
+ "Gadget",
141
+ "=COUNTIFS($C$2:$C$11,G3)",
142
+ "=AVERAGEIFS($D$2:$D$11,$C$2:$C$11,G3)"
143
+ ]
144
+ ]
145
+ }
146
+ }
147
+ ]
148
+ }
@@ -0,0 +1,128 @@
1
+ {
2
+ "prompt": "Add month-over-month growth in column C (header in C1) as a fraction (0.1 for 10 %), with formulas. January has no previous month.",
3
+ "fixture": {
4
+ "editor": "spreadsheet",
5
+ "rows": [
6
+ [
7
+ "Month",
8
+ "Revenue"
9
+ ],
10
+ [
11
+ "Jan",
12
+ 100
13
+ ],
14
+ [
15
+ "Feb",
16
+ 110
17
+ ],
18
+ [
19
+ "Mar",
20
+ 99
21
+ ],
22
+ [
23
+ "Apr",
24
+ 121
25
+ ],
26
+ [
27
+ "May",
28
+ 121
29
+ ],
30
+ [
31
+ "Jun",
32
+ 133
33
+ ]
34
+ ]
35
+ },
36
+ "checks": [
37
+ {
38
+ "why": "growth for February to June",
39
+ "tool": "get_range",
40
+ "args": {
41
+ "range": "C3:C7"
42
+ },
43
+ "expect": {
44
+ "values": {
45
+ "$expr": "[0.1, -0.1, 0.2222, 0, 0.0992].every((g, i) => Math.abs(v[i][0] - g) < 0.001)"
46
+ }
47
+ }
48
+ },
49
+ {
50
+ "why": "formulas, not typed numbers",
51
+ "tool": "get_range",
52
+ "args": {
53
+ "range": "C3:C7"
54
+ },
55
+ "expect": {
56
+ "raw": {
57
+ "$every": {
58
+ "$every": {
59
+ "$match": "^="
60
+ }
61
+ }
62
+ }
63
+ }
64
+ },
65
+ {
66
+ "why": "January is left empty, not 0 or an error",
67
+ "tool": "get_range",
68
+ "args": {
69
+ "range": "C2"
70
+ },
71
+ "expect": {
72
+ "values": [
73
+ [
74
+ ""
75
+ ]
76
+ ],
77
+ "errors": []
78
+ }
79
+ },
80
+ {
81
+ "why": "a header",
82
+ "tool": "get_range",
83
+ "args": {
84
+ "range": "C1"
85
+ },
86
+ "expect": {
87
+ "values": [
88
+ [
89
+ {
90
+ "$match": "\\S"
91
+ }
92
+ ]
93
+ ]
94
+ }
95
+ }
96
+ ],
97
+ "solution": [
98
+ {
99
+ "tool": "set_range",
100
+ "args": {
101
+ "start": "C1",
102
+ "values": [
103
+ [
104
+ "Growth"
105
+ ],
106
+ [
107
+ null
108
+ ],
109
+ [
110
+ "=B3/B2-1"
111
+ ],
112
+ [
113
+ "=B4/B3-1"
114
+ ],
115
+ [
116
+ "=B5/B4-1"
117
+ ],
118
+ [
119
+ "=B6/B5-1"
120
+ ],
121
+ [
122
+ "=B7/B6-1"
123
+ ]
124
+ ]
125
+ }
126
+ }
127
+ ]
128
+ }
@@ -0,0 +1,178 @@
1
+ {
2
+ "prompt": "Make a summary of total sales by region next to the data, starting at G1 (a header row, then one row per region), using formulas so it updates when the data changes.",
3
+ "fixture": {
4
+ "editor": "spreadsheet",
5
+ "rows": [
6
+ [
7
+ "Date",
8
+ "Region",
9
+ "Product",
10
+ "Amount"
11
+ ],
12
+ [
13
+ "2026-01-05",
14
+ "North",
15
+ "Widget",
16
+ 120
17
+ ],
18
+ [
19
+ "2026-01-07",
20
+ "South",
21
+ "Gadget",
22
+ 80
23
+ ],
24
+ [
25
+ "2026-01-12",
26
+ "North",
27
+ "Gadget",
28
+ 95
29
+ ],
30
+ [
31
+ "2026-01-15",
32
+ "East",
33
+ "Widget",
34
+ 60
35
+ ],
36
+ [
37
+ "2026-01-20",
38
+ "West",
39
+ "Widget",
40
+ 150
41
+ ],
42
+ [
43
+ "2026-02-02",
44
+ "South",
45
+ "Widget",
46
+ 110
47
+ ],
48
+ [
49
+ "2026-02-08",
50
+ "East",
51
+ "Gadget",
52
+ 70
53
+ ],
54
+ [
55
+ "2026-02-11",
56
+ "North",
57
+ "Widget",
58
+ 130
59
+ ],
60
+ [
61
+ "2026-02-19",
62
+ "West",
63
+ "Gadget",
64
+ 90
65
+ ],
66
+ [
67
+ "2026-02-25",
68
+ "South",
69
+ "Gadget",
70
+ 85
71
+ ]
72
+ ]
73
+ },
74
+ "checks": [
75
+ {
76
+ "why": "one row per region with the right totals",
77
+ "tool": "get_range",
78
+ "args": {
79
+ "range": "G2:H10"
80
+ },
81
+ "expect": {
82
+ "values": {
83
+ "$nonEmpty": {
84
+ "$length": 4,
85
+ "$expr": "[['North',345],['South',275],['East',130],['West',240]].every(([r, t]) => v.some((row) => row[0] === r && row[1] === t))"
86
+ }
87
+ }
88
+ }
89
+ },
90
+ {
91
+ "why": "the totals are formulas over the data, not typed numbers",
92
+ "tool": "get_range",
93
+ "args": {
94
+ "range": "H2:H5"
95
+ },
96
+ "expect": {
97
+ "raw": {
98
+ "$every": {
99
+ "$every": {
100
+ "$match": [
101
+ "^=.*(SUMIFS?|SUMPRODUCT|FILTER)",
102
+ "i"
103
+ ]
104
+ }
105
+ }
106
+ }
107
+ }
108
+ },
109
+ {
110
+ "why": "a header row",
111
+ "tool": "get_range",
112
+ "args": {
113
+ "range": "G1:H1"
114
+ },
115
+ "expect": {
116
+ "values": [
117
+ {
118
+ "$every": {
119
+ "$match": "\\S"
120
+ }
121
+ }
122
+ ]
123
+ }
124
+ },
125
+ {
126
+ "why": "no formula errors",
127
+ "tool": "get_range",
128
+ "args": {
129
+ "range": "G1:H10"
130
+ },
131
+ "expect": {
132
+ "errors": []
133
+ }
134
+ }
135
+ ],
136
+ "solution": [
137
+ {
138
+ "tool": "get_range",
139
+ "args": {
140
+ "range": "A1:D20"
141
+ }
142
+ },
143
+ {
144
+ "tool": "set_range",
145
+ "args": {
146
+ "start": "G1",
147
+ "values": [
148
+ [
149
+ "Region",
150
+ "Total sales"
151
+ ],
152
+ [
153
+ "North",
154
+ "=SUMIFS($D$2:$D$11,$B$2:$B$11,G2)"
155
+ ],
156
+ [
157
+ "South",
158
+ "=SUMIFS($D$2:$D$11,$B$2:$B$11,G3)"
159
+ ],
160
+ [
161
+ "East",
162
+ "=SUMIFS($D$2:$D$11,$B$2:$B$11,G4)"
163
+ ],
164
+ [
165
+ "West",
166
+ "=SUMIFS($D$2:$D$11,$B$2:$B$11,G5)"
167
+ ]
168
+ ]
169
+ }
170
+ },
171
+ {
172
+ "tool": "get_range",
173
+ "args": {
174
+ "range": "G1:H5"
175
+ }
176
+ }
177
+ ]
178
+ }
@@ -0,0 +1,98 @@
1
+ ---
2
+ name: pixerate-spreadsheet-financial-model
3
+ description: The financial model workflow in a Pixerate spreadsheet - inputs (assumptions) in labelled cells kept apart from calculations and outputs, one consistent formula across each row, absolute references to inputs, no hard-coded numbers in formulas, projections that compound, scenario and sensitivity tables, checks that totals tie, and finding the hard-coded or broken cell in an existing model. Use when asked to build or check a financial model, forecast, projection, budget, business case, revenue or cost model, break-even, loan or growth calculation, scenario or what-if table, or to find why a model does not update, or when get_range and set_range are available. Load with pixerate-spreadsheet, which lists every tool.
4
+ metadata:
5
+ kind: workflow
6
+ reference: pixerate-spreadsheet
7
+ ---
8
+
9
+ # Building a financial model
10
+
11
+ A model is inputs, calculations and outputs. Inputs are the assumptions a person changes
12
+ (starting revenue, growth, prices); calculations turn them into numbers period by period;
13
+ outputs are what the reader looks at (totals, year-5 revenue, profit). Every calculation
14
+ refers to inputs by absolute reference and never contains a typed number, so changing one
15
+ assumption flows through everything.
16
+
17
+ This skill is the method. `pixerate-spreadsheet` has the full tool reference and functions.
18
+
19
+ ## Goal and quality bar
20
+
21
+ - Inputs live in their own labelled cells at the top (or on their own block), one assumption per
22
+ cell, with units in the label.
23
+ - Each row of the projection uses one formula pattern across its periods, referring to inputs
24
+ absolutely (`$B$2`) and to the previous period relatively (`B5`).
25
+ - No hard-coded numbers in calculation formulas (no `*1.08`, no `+5000`); constants that matter
26
+ are inputs.
27
+ - Outputs are formulas; checks tie (subtotals add to totals, a balance closes).
28
+ - No formula errors; the model gives the expected value for a case you can work out by hand.
29
+
30
+ ## Inputs to confirm
31
+
32
+ - **Assumptions** and their values and units (growth as 0.08 or 8 %, currency).
33
+ - **Periods** (years or months) and how many.
34
+ - **Layout** the user expects (where inputs, the projection and outputs go).
35
+ - **Scenarios** to compare (low, base, high).
36
+
37
+ ## Working method
38
+
39
+ 1. **Read** what exists: `get_range` the whole model area, `raw` as well as `values`.
40
+ **Check:** you can list the inputs, the calculation rows and the outputs.
41
+ 2. **Inputs block.** Labels in column A, values in B (`set_range` in one call). Rates as
42
+ fractions (0.08), money as plain numbers with the currency in the label.
43
+ 3. **Timeline.** A header row of periods (years 1-5 in B4:F4).
44
+ 4. **Calculation rows.** One `set_range` per row: the first period from an input (`=$B$1`),
45
+ later periods from the previous one and the input (`=B5*(1+$B$2)`), the same pattern across.
46
+ **Check:** `get_range` values match a hand calculation (100,000 at 8 % is 108,000, then
47
+ 116,640); `errors` empty.
48
+ 5. **Outputs and checks.** Totals (`=SUM(B5:F5)`), year-N values, and a check cell that should be
49
+ 0 or TRUE (`=SUM(B7:F7)-B9`).
50
+ 6. **Scenarios.** A small table with the scenario inputs in one column and the output formula
51
+ next to each, referring to the starting input absolutely and its own scenario cell
52
+ relatively (`=$B$1*(1+A9)^4` for year 5).
53
+ **Check:** the base scenario equals the main model's output.
54
+ 7. **Audit.** Read `raw` across every calculation row: each should match its neighbours' pattern;
55
+ a formula with a typed number or a different reference is the bug.
56
+
57
+ ## Decision table
58
+
59
+ | Need | Formula pattern |
60
+ | --- | --- |
61
+ | Compound growth period on period | `=prev*(1+$rate$)` |
62
+ | Value at period n directly | `=$start$*(1+$rate$)^(n-1)` |
63
+ | Cost as a share of revenue | `=revenue_cell*$cost_share$` |
64
+ | Fixed cost each period | `=$fixed_cost$` |
65
+ | Running total | `=prev_total+this_period` |
66
+ | Profit | `=revenue-costs` |
67
+ | Scenario output | Input in its own cell per scenario, output formula next to it |
68
+ | Something does not update | Find the formula with a typed number or a missing `$` |
69
+
70
+ ## Recipes
71
+
72
+ **Five-year projection.** `set_range` from A1: `[["Starting revenue", 100000], ["Growth", 0.08],
73
+ [], ["Year", 1, 2, 3, 4, 5], ["Revenue", "=$B$1", "=B5*(1+$B$2)", "=C5*(1+$B$2)",
74
+ "=D5*(1+$B$2)", "=E5*(1+$B$2)"]]`.
75
+
76
+ **Find a hard-coded number.** `get_range` the calculation rows; compare each `raw` with its
77
+ neighbours; replace the odd one with the pattern (`set_cell`).
78
+
79
+ **Scenarios.** From A8: `[["Growth", "Year 5 revenue"], [0.05, "=$B$1*(1+A9)^4"], [0.08,
80
+ "=$B$1*(1+A10)^4"], [0.12, "=$B$1*(1+A11)^4"]]`.
81
+
82
+ ## Pitfalls
83
+
84
+ - **Typed numbers in formulas** (`=C5*1.08`) do not follow the input; the classic broken model.
85
+ - **Relative input references** shift as formulas copy across; inputs need `$`.
86
+ - **Mixed patterns in a row** hide errors; one formula pattern per row.
87
+ - **Percent confusion**: 8 typed as a rate is 800 %; write 0.08 or `8%`.
88
+ - **Circular references** (`#CYCLE!`) from a total that includes itself.
89
+ - **`set_range` does not shift references** the way fill does; write each cell's own.
90
+ - **Outputs typed as values** break when an input changes.
91
+
92
+ ## Tools used
93
+
94
+ - `get_range`: read `raw` and `values`, check results and errors
95
+ - `set_range`: write the inputs, timeline, rows and scenario tables, one undo step each
96
+ - `set_cell`: fix one formula
97
+ - `insert_rows`, `insert_columns`: add periods or lines
98
+ - `undo_spreadsheet`: take back a step
@@ -0,0 +1,113 @@
1
+ {
2
+ "prompt": "Something in this revenue model doesn't update when I change the growth rate in B2. Find and fix it.",
3
+ "fixture": {
4
+ "editor": "spreadsheet",
5
+ "rows": [
6
+ [
7
+ "Starting revenue",
8
+ 100000,
9
+ null,
10
+ null,
11
+ null,
12
+ null
13
+ ],
14
+ [
15
+ "Growth",
16
+ 0.08,
17
+ null,
18
+ null,
19
+ null,
20
+ null
21
+ ],
22
+ [
23
+ null,
24
+ null,
25
+ null,
26
+ null,
27
+ null,
28
+ null
29
+ ],
30
+ [
31
+ "Year",
32
+ 1,
33
+ 2,
34
+ 3,
35
+ 4,
36
+ 5
37
+ ],
38
+ [
39
+ "Revenue",
40
+ "=$B$1",
41
+ "=B5*(1+$B$2)",
42
+ "=C5*1.08",
43
+ "=D5*(1+$B$2)",
44
+ "=E5*(1+$B$2)"
45
+ ]
46
+ ]
47
+ },
48
+ "checks": [
49
+ {
50
+ "why": "no typed growth rate left in the projection",
51
+ "tool": "get_range",
52
+ "args": {
53
+ "range": "C5:F5"
54
+ },
55
+ "expect": {
56
+ "raw": [
57
+ {
58
+ "$every": {
59
+ "$match": "^=.*\\$?B\\$?2",
60
+ "$not": {
61
+ "$match": "1\\.08"
62
+ }
63
+ }
64
+ }
65
+ ]
66
+ }
67
+ },
68
+ {
69
+ "why": "the values are unchanged at 8 %",
70
+ "tool": "get_range",
71
+ "args": {
72
+ "range": "F5"
73
+ },
74
+ "expect": {
75
+ "values": {
76
+ "$expr": "Math.abs(v[0][0] - 136048.896) < 0.01"
77
+ }
78
+ }
79
+ },
80
+ {
81
+ "why": "the inputs are untouched",
82
+ "tool": "get_range",
83
+ "args": {
84
+ "range": "B1:B2"
85
+ },
86
+ "expect": {
87
+ "values": [
88
+ [
89
+ 100000
90
+ ],
91
+ [
92
+ 0.08
93
+ ]
94
+ ]
95
+ }
96
+ }
97
+ ],
98
+ "solution": [
99
+ {
100
+ "tool": "get_range",
101
+ "args": {
102
+ "range": "A1:F5"
103
+ }
104
+ },
105
+ {
106
+ "tool": "set_cell",
107
+ "args": {
108
+ "cell": "D5",
109
+ "value": "=C5*(1+$B$2)"
110
+ }
111
+ }
112
+ ]
113
+ }
@@ -0,0 +1,118 @@
1
+ {
2
+ "prompt": "Build a five-year revenue projection: starting revenue 100,000 in year 1, growing 8 % a year. Put the inputs at the top (labels in column A, values in column B: starting revenue in B1, growth in B2) and the projection in rows 4-5 (years 1-5 in B4:F4, revenue in B5:F5), so I can change the growth rate in one place.",
3
+ "fixture": {
4
+ "editor": "spreadsheet",
5
+ "rows": [
6
+ [
7
+ ""
8
+ ]
9
+ ]
10
+ },
11
+ "checks": [
12
+ {
13
+ "why": "the inputs are in their own cells",
14
+ "tool": "get_range",
15
+ "args": {
16
+ "range": "B1:B2"
17
+ },
18
+ "expect": {
19
+ "values": [
20
+ [
21
+ 100000
22
+ ],
23
+ [
24
+ 0.08
25
+ ]
26
+ ]
27
+ }
28
+ },
29
+ {
30
+ "why": "the projection compounds at the input rate",
31
+ "tool": "get_range",
32
+ "args": {
33
+ "range": "B5:F5"
34
+ },
35
+ "expect": {
36
+ "values": {
37
+ "$expr": "[100000, 108000, 116640, 125971.2, 136048.896].every((x, i) => Math.abs(v[0][i] - x) < 0.01)"
38
+ }
39
+ }
40
+ },
41
+ {
42
+ "why": "later years are formulas that use the growth input (B2), not a typed rate",
43
+ "tool": "get_range",
44
+ "args": {
45
+ "range": "C5:F5"
46
+ },
47
+ "expect": {
48
+ "raw": [
49
+ {
50
+ "$every": {
51
+ "$match": "^=.*\\$?B\\$?2"
52
+ }
53
+ }
54
+ ]
55
+ }
56
+ },
57
+ {
58
+ "why": "no formula errors",
59
+ "tool": "get_range",
60
+ "args": {
61
+ "range": "A1:F5"
62
+ },
63
+ "expect": {
64
+ "errors": []
65
+ }
66
+ }
67
+ ],
68
+ "solution": [
69
+ {
70
+ "tool": "set_range",
71
+ "args": {
72
+ "start": "A1",
73
+ "values": [
74
+ [
75
+ "Starting revenue",
76
+ 100000,
77
+ null,
78
+ null,
79
+ null,
80
+ null
81
+ ],
82
+ [
83
+ "Growth",
84
+ 0.08,
85
+ null,
86
+ null,
87
+ null,
88
+ null
89
+ ],
90
+ [
91
+ null,
92
+ null,
93
+ null,
94
+ null,
95
+ null,
96
+ null
97
+ ],
98
+ [
99
+ "Year",
100
+ 1,
101
+ 2,
102
+ 3,
103
+ 4,
104
+ 5
105
+ ],
106
+ [
107
+ "Revenue",
108
+ "=$B$1",
109
+ "=B5*(1+$B$2)",
110
+ "=C5*(1+$B$2)",
111
+ "=D5*(1+$B$2)",
112
+ "=E5*(1+$B$2)"
113
+ ]
114
+ ]
115
+ }
116
+ }
117
+ ]
118
+ }
@@ -0,0 +1,136 @@
1
+ {
2
+ "prompt": "Below the model, add a small scenario table from A8: the year-5 revenue at 5 %, 8 % and 12 % growth (growth rates in A9:A11, year-5 revenue next to each in B9:B11), with formulas from the starting revenue in B1.",
3
+ "fixture": {
4
+ "editor": "spreadsheet",
5
+ "rows": [
6
+ [
7
+ "Starting revenue",
8
+ 100000,
9
+ null,
10
+ null,
11
+ null,
12
+ null
13
+ ],
14
+ [
15
+ "Growth",
16
+ 0.08,
17
+ null,
18
+ null,
19
+ null,
20
+ null
21
+ ],
22
+ [
23
+ null,
24
+ null,
25
+ null,
26
+ null,
27
+ null,
28
+ null
29
+ ],
30
+ [
31
+ "Year",
32
+ 1,
33
+ 2,
34
+ 3,
35
+ 4,
36
+ 5
37
+ ],
38
+ [
39
+ "Revenue",
40
+ "=$B$1",
41
+ "=B5*(1+$B$2)",
42
+ "=C5*(1+$B$2)",
43
+ "=D5*(1+$B$2)",
44
+ "=E5*(1+$B$2)"
45
+ ]
46
+ ]
47
+ },
48
+ "checks": [
49
+ {
50
+ "why": "the three growth rates",
51
+ "tool": "get_range",
52
+ "args": {
53
+ "range": "A9:A11"
54
+ },
55
+ "expect": {
56
+ "values": [
57
+ [
58
+ 0.05
59
+ ],
60
+ [
61
+ 0.08
62
+ ],
63
+ [
64
+ 0.12
65
+ ]
66
+ ]
67
+ }
68
+ },
69
+ {
70
+ "why": "year-5 revenue for each scenario",
71
+ "tool": "get_range",
72
+ "args": {
73
+ "range": "B9:B11"
74
+ },
75
+ "expect": {
76
+ "values": {
77
+ "$expr": "[121550.625, 136048.896, 157351.936].every((x, i) => Math.abs(v[i][0] - x) < 0.01)"
78
+ }
79
+ }
80
+ },
81
+ {
82
+ "why": "formulas from the starting revenue",
83
+ "tool": "get_range",
84
+ "args": {
85
+ "range": "B9:B11"
86
+ },
87
+ "expect": {
88
+ "raw": {
89
+ "$every": {
90
+ "$every": {
91
+ "$match": "^=.*\\$?B\\$?1"
92
+ }
93
+ }
94
+ }
95
+ }
96
+ },
97
+ {
98
+ "why": "the base model still uses B2",
99
+ "tool": "get_range",
100
+ "args": {
101
+ "range": "F5"
102
+ },
103
+ "expect": {
104
+ "values": {
105
+ "$expr": "Math.abs(v[0][0] - 136048.896) < 0.01"
106
+ }
107
+ }
108
+ }
109
+ ],
110
+ "solution": [
111
+ {
112
+ "tool": "set_range",
113
+ "args": {
114
+ "start": "A8",
115
+ "values": [
116
+ [
117
+ "Growth",
118
+ "Year 5 revenue"
119
+ ],
120
+ [
121
+ 0.05,
122
+ "=$B$1*(1+A9)^4"
123
+ ],
124
+ [
125
+ 0.08,
126
+ "=$B$1*(1+A10)^4"
127
+ ],
128
+ [
129
+ 0.12,
130
+ "=$B$1*(1+A11)^4"
131
+ ]
132
+ ]
133
+ }
134
+ }
135
+ ]
136
+ }