@salesforce/afv-skills 1.59.0 → 1.61.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.
Files changed (174) hide show
  1. package/package.json +1 -1
  2. package/skills/agentforce-architecture-analyze/SKILL.md +66 -17
  3. package/skills/agentforce-architecture-analyze/assets/cli/show_access_token.yaml +4 -7
  4. package/skills/agentforce-architecture-analyze/references/contract.json +1 -1
  5. package/skills/agentforce-architecture-analyze/scripts/emit_env.py +2 -2
  6. package/skills/agentforce-architecture-analyze/scripts/main.py +40 -44
  7. package/skills/agentforce-architecture-analyze/scripts/rest_client.py +11 -3
  8. package/skills/agentforce-architecture-analyze/scripts/sf_cli.py +77 -10
  9. package/skills/agentforce-architecture-analyze/scripts/tests/test_end_to_end_fixture.py +1 -0
  10. package/skills/agentforce-architecture-analyze/scripts/tests/test_flow_children_inflation.py +1 -0
  11. package/skills/agentforce-architecture-analyze/scripts/tests/test_main_pipeline.py +53 -2
  12. package/skills/agentforce-architecture-analyze/scripts/tests/test_resolve_creds.py +184 -36
  13. package/skills/agentforce-architecture-analyze/scripts/tests/test_rest_client.py +17 -0
  14. package/skills/agentforce-architecture-analyze/scripts/tests/test_sf_cli.py +165 -11
  15. package/skills/agentforce-architecture-analyze/scripts/tests/test_skill_md_pipeline_block.py +319 -0
  16. package/skills/agentforce-d360-analyze/SKILL.md +28 -10
  17. package/skills/agentforce-d360-analyze/scripts/dc.py +200 -50
  18. package/skills/agentforce-d360-analyze/scripts/discover_sessions.py +10 -6
  19. package/skills/agentforce-d360-analyze/scripts/fetch_dc.py +22 -6
  20. package/skills/agentforce-d360-analyze/scripts/resolve_session.py +21 -13
  21. package/skills/agentforce-d360-analyze/scripts/tests/test_dc_and_resolve_session.py +298 -76
  22. package/skills/agentforce-d360-analyze/scripts/tests/test_default_target_org.py +432 -0
  23. package/skills/agentforce-d360-analyze/scripts/tests/test_discover_sessions.py +1 -1
  24. package/skills/agentforce-d360-analyze/scripts/tests/test_resolve_session_main.py +9 -3
  25. package/skills/agentforce-d360-analyze/scripts/tests/test_skill_md_prefix_block.py +176 -0
  26. package/skills/consumer-goods-accruals-datakit-deploy/SKILL.md +17 -27
  27. package/skills/consumer-goods-accruals-datakit-deploy/scripts/setup.js +7 -17
  28. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/00-check-prerequisites.js +47 -63
  29. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/01-download-static-resource.js +10 -13
  30. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/02-replace-org-id.js +46 -46
  31. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/03-deploy-metadata.js +4 -21
  32. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/06-create-dataspace.js +58 -82
  33. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/07-deploy-accruals-reports.js +16 -315
  34. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/08-deploy-ui.js +9 -24
  35. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/09-completion.js +44 -44
  36. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/utils.js +11 -63
  37. package/skills/consumer-goods-rtr-datacloud-export-configure/SKILL.md +2 -1
  38. package/skills/consumer-goods-tpe-dashboard-configure/SKILL.md +2 -1
  39. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/SKILL.md +2 -1
  40. package/skills/consumer-goods-tpe-datakit-deploy/SKILL.md +2 -1
  41. package/skills/data360-schema-get/SKILL.md +3 -3
  42. package/skills/design-systems-slds-validate/references/report-format.md +1 -1
  43. package/skills/dx-org-manage/SKILL.md +1 -0
  44. package/skills/dx-org-shape-manage/SKILL.md +1 -0
  45. package/skills/dx-org-snapshot-manage/SKILL.md +172 -0
  46. package/skills/dx-org-snapshot-manage/examples/create_error_output.json +16 -0
  47. package/skills/dx-org-snapshot-manage/examples/create_success_output.json +19 -0
  48. package/skills/dx-org-snapshot-manage/examples/delete_output.json +9 -0
  49. package/skills/dx-org-snapshot-manage/examples/get_output.json +19 -0
  50. package/skills/dx-org-snapshot-manage/examples/list_output.json +36 -0
  51. package/skills/dx-org-snapshot-manage/references/cli_flags.md +179 -0
  52. package/skills/dx-org-snapshot-manage/references/snapshot_usage.md +55 -0
  53. package/skills/dx-org-trial-expiration-check/SKILL.md +1 -1
  54. package/skills/dx-org-trial-expiration-check/scripts/check_expiration.sh +2 -2
  55. package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +7 -5
  56. package/skills/education-cloud-student-recruitment-agent-configure/references/prerequisites.md +11 -11
  57. package/skills/experience-content-media-image-generate/SKILL.md +83 -0
  58. package/skills/experience-content-media-image-generate/scripts/download-image.sh +84 -0
  59. package/skills/external-diagram-mermaid-generate/SKILL.md +12 -12
  60. package/skills/field-service-data-capture-migrate/references/component-mapping.md +1 -1
  61. package/skills/field-service-data-capture-migrate/references/fsm-dc-capability-catalog.md +1 -2
  62. package/skills/field-service-data-capture-migrate/references/known-deploy-errors.md +1 -0
  63. package/skills/field-service-data-capture-migrate/references/transformer-rules.md +1 -1
  64. package/skills/field-service-data-capture-migrate/scripts/transform_flow.py +12 -1
  65. package/skills/field-service-data-capture-migrate/scripts/vendor/build_flow.py +4 -1
  66. package/skills/field-service-setup-orchestrator-get/SKILL.md +0 -1
  67. package/skills/insurance-brokerage-agency-billing-configure/SKILL.md +233 -0
  68. package/skills/insurance-brokerage-agency-billing-configure/references/accounting-setup.md +117 -0
  69. package/skills/insurance-brokerage-agency-billing-configure/references/billing-rules-config.md +237 -0
  70. package/skills/insurance-brokerage-agency-billing-configure/references/billing-settings.md +271 -0
  71. package/skills/insurance-brokerage-agency-billing-configure/references/demo-policy-config.md +138 -0
  72. package/skills/insurance-brokerage-agency-billing-configure/references/org-defaults.md +64 -0
  73. package/skills/insurance-brokerage-agency-billing-configure/references/permissions-config.md +226 -0
  74. package/skills/insurance-brokerage-agency-billing-configure/references/prerequisites.md +142 -0
  75. package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +3 -2
  76. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-execution-state-and-recovery.md +3 -3
  77. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-overview.md +68 -45
  78. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-visit-creation-data.md +17 -18
  79. package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +16 -3
  80. package/skills/life-sciences-kam-coordinate/references/stage-5-data-creation-data.md +16 -13
  81. package/skills/marketing-message-test/SKILL.md +194 -0
  82. package/skills/marketing-message-test/references/api-contract.md +333 -0
  83. package/skills/omnistudio-callable-apex-generate/SKILL.md +3 -3
  84. package/skills/omnistudio-datamapper-generate/SKILL.md +6 -6
  85. package/skills/omnistudio-datapacks-deploy/SKILL.md +11 -11
  86. package/skills/omnistudio-dependencies-analyze/SKILL.md +5 -5
  87. package/skills/omnistudio-epc-catalog-generate/SKILL.md +6 -6
  88. package/skills/omnistudio-flexcard-generate/SKILL.md +6 -6
  89. package/skills/omnistudio-integration-procedure-generate/SKILL.md +6 -6
  90. package/skills/omnistudio-omniscript-generate/SKILL.md +6 -5
  91. package/skills/platform-apex-logs-debug/SKILL.md +1 -0
  92. package/skills/platform-metadata-deploy/SKILL.md +1 -0
  93. package/skills/platform-models-api-configure/SKILL.md +5 -5
  94. package/skills/platform-soql-query/SKILL.md +1 -0
  95. package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +17 -6
  96. package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +107 -192
  97. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone-action.json +6 -6
  98. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +49 -20
  99. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +4 -4
  100. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +224 -290
  101. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/workflow.md +295 -0
  102. package/skills/service-itsm-incident-priority-configure/SKILL.md +1 -1
  103. package/skills/service-itsm-slack-itservice-configure/SKILL.md +2 -1
  104. package/skills/service-itsm-slack-itservice-configure/references/manage-slack-connection.md +2 -2
  105. package/skills/service-omni-channel-setup-coordinate/SKILL.md +20 -13
  106. package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +5 -55
  107. package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +0 -38
  108. package/skills/service-omni-command-center-analyze/SKILL.md +2 -3
  109. package/skills/service-omni-command-center-analyze/scripts/analyze.sh +3 -3
  110. package/skills/tableau-next-asset-create/README.md +65 -0
  111. package/skills/tableau-next-asset-create/SKILL.md +409 -0
  112. package/skills/tableau-next-asset-create/references/admin-users.md +149 -0
  113. package/skills/tableau-next-asset-create/references/ai-readiness.md +355 -0
  114. package/skills/tableau-next-asset-create/references/alerts.md +111 -0
  115. package/skills/tableau-next-asset-create/references/build-workflow.md +819 -0
  116. package/skills/tableau-next-asset-create/references/dashboard-authoring.md +302 -0
  117. package/skills/tableau-next-asset-create/references/dashboard-design-principles.md +352 -0
  118. package/skills/tableau-next-asset-create/references/data-transform-gotchas.md +210 -0
  119. package/skills/tableau-next-asset-create/references/data-understanding.md +387 -0
  120. package/skills/tableau-next-asset-create/references/deep-reference-index.md +74 -0
  121. package/skills/tableau-next-asset-create/references/destructive-operations.md +62 -0
  122. package/skills/tableau-next-asset-create/references/edit-dashboard.md +273 -0
  123. package/skills/tableau-next-asset-create/references/edit-visualization.md +529 -0
  124. package/skills/tableau-next-asset-create/references/empty-source-handling.md +104 -0
  125. package/skills/tableau-next-asset-create/references/ingest-and-metric-gotchas.md +638 -0
  126. package/skills/tableau-next-asset-create/references/insight-bundle.md +69 -0
  127. package/skills/tableau-next-asset-create/references/large-flat-file-handling.md +331 -0
  128. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/_envelope.json +54 -0
  129. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/_shared-widget-fields.json +122 -0
  130. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/add_page.json +29 -0
  131. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/delete_page.json +27 -0
  132. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/discard_dashboard.json +11 -0
  133. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/move_widget.json +30 -0
  134. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/move_widget_to_page.json +30 -0
  135. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/place_widget_on_page.json +30 -0
  136. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/remove_widget_from_dashboard.json +16 -0
  137. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/remove_widget_from_page.json +26 -0
  138. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/rename_page.json +28 -0
  139. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/save_dashboard.json +18 -0
  140. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/undo.json +11 -0
  141. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_button_widget.json +69 -0
  142. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_container_widget.json +88 -0
  143. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_filter_widget.json +146 -0
  144. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_metric_widget.json +78 -0
  145. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_navigation_widget.json +80 -0
  146. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_parameter_widget.json +160 -0
  147. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_text_widget.json +103 -0
  148. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_visualization_widget.json +68 -0
  149. package/skills/tableau-next-asset-create/references/sdm-tool-reference.md +721 -0
  150. package/skills/tableau-next-asset-create/references/semantic-query-and-enrichment.md +355 -0
  151. package/skills/tableau-next-asset-create/references/shared-gates.md +322 -0
  152. package/skills/tableau-next-asset-create/references/sharing-and-promotion.md +306 -0
  153. package/skills/tableau-next-asset-create/references/tasks/ai-readiness-audit.md +51 -0
  154. package/skills/tableau-next-asset-create/references/tasks/analyze-data.md +48 -0
  155. package/skills/tableau-next-asset-create/references/tasks/build-dashboard.md +66 -0
  156. package/skills/tableau-next-asset-create/references/tasks/build-end-to-end.md +73 -0
  157. package/skills/tableau-next-asset-create/references/tasks/create-data-transform.md +51 -0
  158. package/skills/tableau-next-asset-create/references/tasks/create-viz.md +58 -0
  159. package/skills/tableau-next-asset-create/references/tasks/edit-dashboard.md +80 -0
  160. package/skills/tableau-next-asset-create/references/tasks/edit-sdm.md +106 -0
  161. package/skills/tableau-next-asset-create/references/tasks/edit-viz.md +40 -0
  162. package/skills/tableau-next-asset-create/references/tasks/enrich-model.md +68 -0
  163. package/skills/tableau-next-asset-create/references/tasks/ingest-flat-file.md +68 -0
  164. package/skills/tableau-next-asset-create/references/tasks/manage-alert.md +35 -0
  165. package/skills/tableau-next-asset-create/references/tasks/profile-dataset.md +47 -0
  166. package/skills/tableau-next-asset-create/references/tasks/promote-or-reuse.md +46 -0
  167. package/skills/tableau-next-asset-create/references/tasks/provision-user.md +39 -0
  168. package/skills/tableau-next-asset-create/references/tasks/query-model.md +92 -0
  169. package/skills/tableau-next-asset-create/references/tasks/review-verified-questions.md +42 -0
  170. package/skills/tableau-next-asset-create/references/tasks/share-asset.md +37 -0
  171. package/skills/tableau-next-asset-create/references/verified-questions.md +125 -0
  172. package/skills/tableau-next-asset-create/references/viz-authoring.md +992 -0
  173. package/skills/service-de-channel-consent-configure/SKILL.md +0 -230
  174. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +0 -102
@@ -0,0 +1,529 @@
1
+ # Editing an Existing Visualization — `edit_visualization` / `update_visualization`
2
+
3
+ Load this when the request mutates a visualization that **already exists** (add/
4
+ remove/replace a shelf field, filter, forecast, sort, mark encoding, mark type, dual
5
+ axis, reference line, quick table calc, axis title, field label, field/axis
6
+ visibility, number/date format, table layout/totals, or fit) — not for building a new
7
+ chart (`viz-authoring.md`/`tasks/create-viz.md` instead).
8
+
9
+ ## Contents
10
+
11
+ - 1. Contract
12
+ - 2. Operation-type quick reference
13
+ - 3. Filter-operator rules
14
+ - 4. Filter buckets and `{model:}` vs `{expression:}`
15
+ - 5. Add-then-filter heuristic
16
+ - 6. Sort operations — `targetField` vs `byField`
17
+ - 7. Reference lines: `field.function` vs `line.function`
18
+ - 8. Table-layout shelf operations (Columns/Groups)
19
+ - 9. Table totals and subtotals
20
+ - 10. Layout and fit
21
+ - 11. Field label, field-label visibility, axis visibility, number/date format
22
+ - 12. Measure Values shelf editing
23
+ - 13. Table calcs — `quickTableCalc` types and `computeUsing`
24
+ - 14. Logical views and HLV field naming
25
+ - 15. Persistence handoff — `update_visualization`
26
+
27
+ ---
28
+
29
+ ## 1. Contract
30
+
31
+ `edit_visualization`'s **required inputs are `visualizationId` and
32
+ `editOperations`** — never pass a raw `visualizationMetadata` blob as input, the
33
+ server resolves the metadata itself from the id:
34
+
35
+ - `visualizationId` — the id from `get_visualization` or a prior edit's response.
36
+ After `create_visualization`, call `get_visualization` first — create returns
37
+ only `{id, label, name}`, no editable metadata to chain from. If a call reports
38
+ the id was **not found**, call `get_visualization` with that id to reload it,
39
+ then retry.
40
+ - `editOperations` is a **JSON string** encoding an array of `{operationType, params}`
41
+ objects — build the array, then `JSON.stringify` it; never pass a raw array.
42
+ - Operations run **in order**, each seeing the previous one's result.
43
+ - The batch is **all-or-nothing** — one failing operation means none are applied.
44
+ - The result **renders inline automatically** — never call `render_visualization`
45
+ afterward.
46
+ - The result is **never persisted**. The response's `outputValues.visualizationMetadata`
47
+ holds the edited definition (for `update_visualization`, see §15) — there is no
48
+ separate top-level id field in the response. The server keeps per-`visualizationId`
49
+ working state: reuse the **same `visualizationId`** you already have (no need to
50
+ re-read it from the response) to chain straight into another `edit_visualization`
51
+ call, and each call's edits accumulate on top of the previous ones for that id.
52
+
53
+ ## 2. Operation-type quick reference
54
+
55
+ Every operation targets a field already on a shelf/encoding/axis by
56
+ `{fieldName, objectName?, function?}` (`function` disambiguates same-name copies
57
+ under different aggregations, e.g. Sum vs Avg of the same measure — never a display
58
+ word like "Year", use the canonical values in the Field-functions note below).
59
+
60
+ | Category | Operations | Sharpest constraint |
61
+ |---|---|---|
62
+ | Shelf fields | `addFieldToRowsOrColumns`, `removeFieldFromRowsOrColumns`, `replaceFieldFromRowsOrColumns`, `changeFieldOnRowsOrColumns`, `reorderFieldsOnRowsOrColumns` | `changeFieldOnRowsOrColumns` changes an existing field's aggregation, discrete-vs-continuous display, and/or role in place — never adds/removes/swaps. Params `{field, shelfType, occurrence?, function?, displayCategory?, role?}` — supply at least one of `function` (an aggregation), `displayCategory` (`Discrete`/`Continuous`), or `role` (`Dimension`/`Measure`); omitted ones unchanged. Changing to a Measure requires a `function` (`Sum`/`Avg`/`Median`/`Min`/`Max`/`Count`/`CountD`/`Stdev`/`Stdevp`/`Var`/`Varp`); changing to a Dimension drops it. Examples: "average Sales instead of summing it" → `{field: {fieldName: "Sales", objectName: "Orders", function: "Sum"}, shelfType: "columns", function: "Avg"}`; "make Sales on rows discrete" → `{field: {...Sales}, shelfType: "rows", displayCategory: "Discrete"}`; "turn Category into a measure counting distinct values" → `{field: {fieldName: "Category", objectName: "Orders"}, shelfType: "columns", role: "Measure", function: "CountD"}`. Use `occurrence` (1-based) when the same field repeats on a shelf. `addFieldToRowsOrColumns` takes an optional 0-based `insertPosition` (appended when omitted). `reorderFieldsOnRowsOrColumns` moves a field already on the shelf to a new `insertPosition` without adding/removing anything — `insertPosition` is counted over the shelf's fields **after the moved field is taken out first**, so any spot right of its old position shifts left by one; `0` is the front, the shelf's last index the back. |
63
+ | Label | `changeLabel` | Renames the **visualization**, not on-chart title text. For a *field's* displayed caption use `editFieldLabel` (§11), not this. |
64
+ | Filters | `addFilter`, `editFilter`, `removeFilter` | See §3-5. The field reference lives inside a **`fields` array nested under a `filter` object** — `params: {filter: {fields: [...], operator, values}, bucket}` — never a bare `field:` key. |
65
+ | Forecasts | `addForecast`, `editForecast`, `removeForecast` | Timeseries-only (date field + measure). Omitting `forecastInterval` defaults to 4 periods at the date field's own granularity; `forecastInterval` is `{value, timeUnit}` with `timeUnit` ∈ `Minutes`/`Hours`/`Days`/`Weeks`/`Months`/`Quarters`/`Years` to override the period unit explicitly. Other defaults: `forecastModel` → `HoltWinters` (alternative: `RidgeRegression`); `includeBounds` → `true`; `confidenceLevel` → `95` (also `90`/`99`); `fillNullsWithZero` → `false`; `granularity` supports only `InferFromData`. `ignoreLastPeriods` (int) excludes trailing partial/incomplete periods from the fit. Use `occurrence` (1-based) when the target measure repeats on the shelf. `removeForecast` with no `measureField` removes ALL forecasts. Examples: "forecast Sales for 6 months" → `addForecast {measureField: {fieldName: "Sales", objectName: "Orders"}, dateDimensionField: {fieldName: "OrderDate", objectName: "Orders"}, forecastInterval: {value: 6, timeUnit: "Months"}}`; "forecast Revenue 12 months ahead using RidgeRegression" → `addForecast {measureField: {fieldName: "Revenue"}, dateDimensionField: {fieldName: "Date"}, forecastInterval: {value: 12, timeUnit: "Months"}, forecastModel: "RidgeRegression"}`; "change the Sales forecast to 12 months" → `editForecast {measureField: {fieldName: "Sales", objectName: "Orders"}, forecastInterval: {value: 12, timeUnit: "Months"}}`; "remove all forecasts" → `removeForecast {}`. |
66
+ | Sort | `sortAscending`, `sortDescending`, `sortByField`, `clearSort` | Only **discrete** fields can be sorted. Target is keyed **`targetField`**, never `field`. See §6. |
67
+ | Mark encodings | `addFieldToEncoding`, `removeFieldFromEncoding`, `replaceFieldOnEncoding` | `encodingType` ∈ Color(1 field)/Label(≤3, oldest dropped)/Tooltip(measure only, unlimited)/Size(1 field)/Detail(unlimited). On a **Table** layout only Color/Tooltip are valid slots (Label/Size/Detail error); a dimension dropped onto Color/Tooltip on a Table is coerced to a measure via `Attr`. "Measure Names" is named as `{field: {type: "MeasureNames"}}`, Color-only. `removeFieldFromEncoding`/`replaceFieldOnEncoding` must name the field **currently on that slot** — an arbitrary shelf field errors; read the current encoding first if unsure. Optional `targetField` edits/retypes only ONE field's mark on a multi-measure viz — that field must already be a mark on the visualization or the operation errors; omit it to apply to all marks. Examples: "color by Region" → `addFieldToEncoding {encodingType: "Color", field: {fieldName: "Region", objectName: "Orders"}}`; "add Revenue to the tooltip" → `addFieldToEncoding {encodingType: "Tooltip", field: {fieldName: "Revenue", objectName: "Orders"}}`; "size the bubbles by Sales" → `addFieldToEncoding {encodingType: "Size", field: {fieldName: "Sales", objectName: "Orders"}}`; "color by Region instead of by measure" → `replaceFieldOnEncoding {encodingType: "Color", existingField: {type: "MeasureNames"}, newField: {fieldName: "Region", objectName: "Orders"}}`. |
68
+ | Mark type | `changeMarkType` | Only changes shape (`Bar`/`Line`/`Circle`/`Square`/`Donut`/`Text`/`automatic`) — never moves/adds fields, so it cannot alone turn one chart type into another that needs a different shelf layout. Same optional `targetField` single-mark-only mechanism as mark encodings above. On a Table layout only `Text`/`Bar` are valid — any other type (including `automatic`) errors. Examples: "draw the marks as lines" → `{markType: "Line"}`; "let the visualization choose" → `{markType: "automatic"}`; "show the table as text" (Table layout) → `{markType: "Text"}`. |
69
+ | Layout and fit | `changeLayout`, `changeVisualizationFit` | See §10. |
70
+ | Dual axis | `addDualAxis`, `removeDualAxis`, `synchronizeAxis` | Both fields must already be **adjacent continuous fields on the same shelf** — add the second field first if missing. `primaryField` is the LEFT field, `secondaryField` the RIGHT (shelf order matters; the secondary moves onto the shared axis). `synchronization` ∈ `"None"` (default; independent scales) / `"ZeroPosition"` (zero lines align) / `"FullRange"` (shared range) — optional on `addDualAxis`, **required on `synchronizeAxis`**. `"ZeroPosition"` is invalid when both fields are the same kind of date function (use `FullRange`/`None`). `removeDualAxis`/`synchronizeAxis` name the same (primary, secondary) pair. When the same name+object+function pair still repeats on the shelf (can't disambiguate primary/secondary otherwise), add `primaryOccurrence`/`secondaryOccurrence` (1-based) to pick which copy. |
71
+ | Reference lines | `addReferenceLine`, `removeReferenceLine`, `editReferenceLine` | VizQL only. See §7 — the single trickiest disambiguation in this tool. |
72
+ | Table calcs | `applyTableCalc`, `editTableCalc`, `removeTableCalc` | Target must already be a shelf measure. `edit`/`removeTableCalc` error if the field has no existing calc. See §13 for `quickTableCalc.type` enum values and `computeUsing`. |
73
+ | Axis titles | `editAxisTitle`, `resetAxisTitle` | Target by field, not by axis position. |
74
+ | Field label | `editFieldLabel` | Renames a field's displayed caption (axis, row/column header, table header) — distinct from `changeLabel` (renames the viz). See §11. |
75
+ | Field-label visibility | `showFieldLabel` | Shows/hides the field labels on a whole shelf. Discrete fields only. See §11. |
76
+ | Axis visibility | `showAxis` | Shows/hides the axis for one continuous rows/columns field. See §11. |
77
+ | Number/date format | `setFieldFormat` | Currency/percentage/abbreviated/decimal-places number formats, or a date template. See §11. |
78
+ | Table-shelf fields | `addFieldToGroupsOrColumns`, `removeFieldFromGroupsOrColumns`, `replaceFieldFromGroupsOrColumns`, `reorderFieldsOnGroupsOrColumns`, `changeFieldOnGroupsOrColumns` | TABLE layout only — the table equivalent of the rows/columns ops above. See §8. |
79
+ | Table totals | `showGrandTotals`, `showSubtotals`, `editGrandTotalsLabel`, `editSubtotalsLabel` | TABLE layout only. See §9 — includes a reset-semantics gotcha (`{}` vs `{label:""}`). |
80
+ | Measure Values | `addFieldToMeasureValues`, `reorderFieldsOnMeasureValues`, `replaceFieldFromMeasureValues`, `removeFieldFromMeasureValues` | VizQL only, and only once the Measure Names/Measure Values construct already exists on the shelf. See §12. |
81
+
82
+ **Field functions** (the `function` on a field object) — use canonical values, never
83
+ display words: aggregations `Sum/Avg/Median/Count/CountD/Min/Max/Stdev/Stdevp/Var/
84
+ Varp/Attr` (`Count`/`CountD`/`Min`/`Max` also apply to dimensions; the rest are
85
+ measures only); date parts `DatePart{Year,Quarter,Month,Week,Day,Hour,Minute}`; date
86
+ truncations `DateTrunc{Year,Quarter,Month,Week,Day}`; fiscal variants of both exist
87
+ (`FiscalDatePartYear`, `FiscalDateTruncQuarter`, …). A date dropped onto a shelf buckets
88
+ by year by default — use `DatePartYear` explicitly for a "broken out by year" ask; for
89
+ finer/date-part filtering use a `DATEPART`/`DATETRUNC` expression (§4), not a shelf
90
+ function.
91
+
92
+ ## 3. Filter-operator rules
93
+
94
+ 1. **Never `Equals`/`DoesNotEquals`.** Always `In`/`NotIn` — `In` with one value is
95
+ the equivalent. "Color is Red" → `operator: "In", values: ["Red"]`.
96
+ 2. **Always the `IgnoreCase` string variant** — `ContainsIgnoreCase`,
97
+ `StartsWithIgnoreCase`, `EndsWithIgnoreCase`, `DoesNotStartWithIgnoreCase`, etc.
98
+ Users almost never intend case-sensitive matching.
99
+ 3. **Never `null`/`undefined` in `values`.** If both bounds of a range can't be
100
+ expressed with concrete values, pick a different operator.
101
+
102
+ Other operator families: comparison (`Between`, `LessThan(OrEqualTo)`,
103
+ `GreaterThan(OrEqualTo)`); null/empty (`IsNull`, `IsNotNull`, `IsEmpty`, `IsNotEmpty`);
104
+ relative-date (no values: `CurrentWeek`/`PreviousMonth`/`NextYear`… · with `values:[N]`:
105
+ `LastNDays`/`NextNWeeks`… — "last/next `<period>`" with no number → singular form,
106
+ with a number → `LastN`/`NextN` form, **except** "last 1 quarter" still uses the `LastN`
107
+ form with `values:[1]`); fiscal-date variants exist but only fire on an explicit
108
+ "fiscal" in the request ("this quarter" ≠ "this fiscal quarter"); date-to-date
109
+ (`CurrentYearToDate`, `PreviousQuarterToDate`, …, plain `{model:}`, no expression);
110
+ Top/Bottom-N (`operator: "AdvancedDimension"`, `bucket: "advancedDimensionFilter"`,
111
+ `topBottomCriteria: {topBottomLimit, isTop, expression}` — always this operator for
112
+ any top/bottom-N ask, never a value filter).
113
+
114
+ ## 4. Filter buckets and `{model:}` vs `{expression:}`
115
+
116
+ Execution order / bucket meaning: `filter` (standard) → `contextFilter` (pre-
117
+ aggregation) → `aggregateFilter` (post-aggregation, e.g. `SUM(Sales) > 1000`) →
118
+ `tableCalcFilter` (on a table calc) → `advancedDimensionFilter` (Top/Bottom-N).
119
+
120
+ Decision rule for the filter's field reference:
121
+
122
+ - **`{model: "Object.Field"}`** — plain row-level comparison, no aggregation keyword.
123
+ "sales > 1000" → `{model: "Orders.Sales"}`.
124
+ - **`{expression: "SUM/AVG/…(...)"}`, `bucket: "aggregateFilter"`** — only when the
125
+ request names an aggregate concept explicitly ("total", "sum of", "average", "count
126
+ of"). Without one of those keywords, always use `{model:}`.
127
+ - **`{expression: "DATEPART(...)"}`/`"DATETRUNC(...)"`** — date-part/date-trunc
128
+ phrasing ("orders from 2020 and 2021", "after June 2022"). `DATEPART` returns an
129
+ integer; `DATETRUNC` returns a date (values as ISO strings).
130
+ - **`{expression: "RUNNING_*/RANK/WINDOW_*(...)"}`, `bucket: "tableCalcFilter"`** —
131
+ running total/avg/rank/window/moving/cumulative phrasing. These always wrap an
132
+ aggregate expression as their inner argument (e.g. `RANK(SUM([Orders].[Profit]),
133
+ 'ASC')`).
134
+
135
+ **Whole-`params` shape.** The field reference (`{model:}` or `{expression:}`) is never
136
+ passed alone — it sits inside a **`fields` array**, itself nested under a **`filter`**
137
+ object, which is a sibling of `bucket` at the top of `params`:
138
+
139
+ ```json
140
+ {"operationType": "addFilter", "params": {
141
+ "bucket": "filter",
142
+ "filter": {"fields": [{"model": "Orders.Region"}], "operator": "In", "values": ["East", "West"]}
143
+ }}
144
+ ```
145
+
146
+ Full worked examples, each showing the complete `params` object:
147
+
148
+ - "sales greater than 1000" (raw field):
149
+ `{filter: {fields: [{model: "Orders.Sales"}], operator: "GreaterThan", values: [1000]}, bucket: "filter"}`
150
+ - "orders from 2020 and 2021":
151
+ `{filter: {fields: [{expression: "DATEPART('year', [Orders].[Order_Date])"}], operator: "In", values: [2020, 2021]}, bucket: "filter"}`
152
+ - "orders after June 2022" (DATETRUNC, ISO values):
153
+ `{filter: {fields: [{expression: "DATETRUNC('month', [Orders].[Order_Date])"}], operator: "GreaterThan", values: ["2022-06-01"]}, bucket: "filter"}`
154
+ - "where total sales > 10000" (aggregate):
155
+ `{filter: {fields: [{expression: "SUM([Orders].[Sales])"}], operator: "GreaterThan", values: [10000]}, bucket: "aggregateFilter"}`
156
+ - "where profit margin > 15%":
157
+ `{filter: {fields: [{expression: "SUM([Orders].[Profit])/SUM([Orders].[Sales])"}], operator: "GreaterThan", values: [0.15]}, bucket: "aggregateFilter"}`
158
+ - "where running sum of sales > 1000":
159
+ `{filter: {fields: [{expression: "RUNNING_SUM(SUM([Orders].[Sales]))"}], operator: "GreaterThan", values: [1000]}, bucket: "tableCalcFilter"}`
160
+ - "top 10 states by sales":
161
+ `{filter: {fields: [{model: "Orders.State"}], operator: "AdvancedDimension", topBottomCriteria: {topBottomLimit: 10, isTop: true, expression: "SUM([Orders].[Sales])"}}, bucket: "advancedDimensionFilter"}`
162
+
163
+ `editFilter` updates an existing filter's expression (same `params` shape, matched
164
+ against the current filter's field). `removeFilter` removes one by field reference:
165
+ `{filterField: {model: "Object.Field"}}`.
166
+
167
+ ## 5. Add-then-filter heuristic
168
+
169
+ Add a field to a shelf **before** filtering it only when the user wants to *display*
170
+ that dimension broken out by value **and** restrict which values show, and it isn't
171
+ already on a shelf. A pure restriction ("filter to top 10 customers", "only show the
172
+ West region", "show orders from this year") is a single `addFilter` — never add a
173
+ field just because a filter names a dimension. If the field is already on a shelf,
174
+ filter/sort it in place.
175
+
176
+ Example (add-then-filter, two chained operations): "I want to see Region in East and
177
+ West" (no Region on a shelf yet) →
178
+ ```json
179
+ [
180
+ {"operationType": "addFieldToRowsOrColumns", "params": {"field": {"fieldName": "Region", "objectName": "Orders"}, "shelfType": "rows"}},
181
+ {"operationType": "addFilter", "params": {"filter": {"fields": [{"model": "Orders.Region"}], "operator": "In", "values": ["East", "West"]}, "bucket": "filter"}}
182
+ ]
183
+ ```
184
+
185
+ ## 6. Sort operations — `targetField` vs `byField`
186
+
187
+ Sorts apply to a **discrete** field already on a shelf (chart rows/columns, or table
188
+ columns/groups — sort works on both layouts). The field being sorted is keyed
189
+ **`targetField`** — never `field` — `{fieldName, objectName?, function?}`. Every copy
190
+ of the field on the shelf stays in sync.
191
+
192
+ - **`sortAscending`/`sortDescending`** sort by the target field's OWN values (A→Z /
193
+ Z→A). If the field is already sorted by another field, these keep that by-field
194
+ sort and only flip direction; pass `sortType: "Alphabetic"` to force a plain
195
+ alphabetic sort and drop an existing by-field sort.
196
+ - **`sortByField`** sorts `targetField` by the AGGREGATED value of a second field,
197
+ `byField` `{fieldName, objectName?, function?}`. `byField.function` is the
198
+ aggregation — measures keep their default when omitted, dimensions default to
199
+ `Count` (dimensions allow only `Count`/`CountD`/`Min`/`Max`). `sortType`: `"Field"`
200
+ (default) or `"Nested"` (ordered within each group). `descending` (boolean) defaults
201
+ `true` — set `false` for smallest-first.
202
+ - **`clearSort`** removes the current sort — just `{targetField}`.
203
+
204
+ Worked examples:
205
+
206
+ - "Sort Category A to Z": `sortAscending {targetField: {fieldName: "Category", objectName: "Orders"}}`
207
+ - "Sort Category Z to A": `sortDescending {targetField: {fieldName: "Category", objectName: "Orders"}}`
208
+ - "Sort Region to ascending alphabetic" (Region currently sorted by another field):
209
+ `sortAscending {targetField: {fieldName: "Region", objectName: "Orders"}, sortType: "Alphabetic"}`
210
+ - "Sort States by total Sales, largest first":
211
+ `sortByField {targetField: {fieldName: "State", objectName: "Orders"}, byField: {fieldName: "Sales", objectName: "Orders", function: "Sum"}}`
212
+ - "Nest a descending sort on Region by sum of Profit":
213
+ `sortByField {targetField: {fieldName: "Region", objectName: "Orders"}, byField: {fieldName: "Profit", objectName: "Orders", function: "Sum"}, sortType: "Nested"}`
214
+ - "Sort Category by count of Orders, smallest first":
215
+ `sortByField {targetField: {fieldName: "Category", objectName: "Orders"}, byField: {fieldName: "Order_Id", objectName: "Orders", function: "Count"}, descending: false}`
216
+ - "Clear the sort on States": `clearSort {targetField: {fieldName: "State", objectName: "Orders"}}`
217
+
218
+ ## 7. Reference lines: `field.function` vs `line.function`
219
+
220
+ The reference-line operations' single trickiest gotcha — two different `function`
221
+ fields that are easy to conflate:
222
+
223
+ - **`field.function`** selects *which shelf pill* the line attaches to (its own
224
+ aggregation) — e.g. when Sum-of-Sales and Avg-of-Sales are both on the shelf.
225
+ A phrase like **"sum of sales"** in a reference-line request names *this* field,
226
+ not the line's statistic.
227
+ - **`line.function`** is the line's *own* computed statistic (an average line, a max
228
+ line) — set it only when the request explicitly names the line's statistic
229
+ ("add an **average** line", "a **max** line").
230
+
231
+ So "add a reference line to Sum of Sales" means: attach to the Sum-of-Sales pill and
232
+ use the *default* line (which is an average line when omitted) — it does **not**
233
+ mean a sum line. Two kinds of line: `{valueType: "Computation", function}` (a
234
+ statistic — `function` ∈ `Sum`/`Avg`/`Min`/`Max` for a measure axis or
235
+ `Max`/`Min`/`Today`/`Now` for a date axis; defaults to average for a measure /
236
+ latest-date for a date field when `line` is omitted entirely) or `{valueType:
237
+ "Value", value}` (a constant — a number for a measure axis or ISO date string for a
238
+ date axis). `line.computationField` (`{fieldName, objectName?, function?}`, need not
239
+ be on a shelf) lets the line compute from a *different* field than the one it's drawn
240
+ on. An optional label lives INSIDE `line`: `labelType` ∈ `None`/`Value`/`Computation`/
241
+ `Custom` (with `customLabel`); `scope` ∈ `Table` (default)/`Pane`/`Cell`; `lineColor`
242
+ is a top-level hex (default gray), accepted by both add and edit. Position a specific line among several on the same field
243
+ with `lineOccurrence` (1-based, counts lines); `occurrence` instead picks which
244
+ *shelf pill* when the field repeats — the two are independent and both may be needed
245
+ ("2nd reference line on the 2nd Sum of Sales" → `occurrence: 2, lineOccurrence: 2`).
246
+ Beyond position, a line can also be disambiguated by matching its current
247
+ `computationFunction`, `value`, `labelText`, `scope`, or `lineValueType` — useful
248
+ when the request describes the line itself ("the average line", "the line labeled
249
+ Goal") rather than its position.
250
+ `removeReferenceLine` with no scope removes every matching line on the field.
251
+ `editReferenceLine` changes exactly ONE line and **errors if the description
252
+ matches more than one** — narrow with `occurrence`/`lineOccurrence`/`scope`/one of
253
+ the descriptive selectors above until it's unambiguous. Each attribute on
254
+ `editReferenceLine` edits independently: omitting `line` keeps the line's current
255
+ value/function; a `line` change keeps the current label unless you also pass
256
+ `labelType`/`customLabel`; passing only `scope` or `lineColor` changes just that
257
+ attribute and leaves everything else (value, label) untouched. A label passed at
258
+ the top level of `params` wins over one nested inside `line`.
259
+
260
+ Worked examples:
261
+
262
+ - "add an average line to Sales" (default line): `addReferenceLine {field: {fieldName: "Sales", objectName: "Orders"}}`
263
+ - "add a target line at 5000 on Sales" (constant): `addReferenceLine {field: {fieldName: "Sales", objectName: "Orders"}, line: {valueType: "Value", value: 5000}}`
264
+ - "add a line at 100 on avg of Sales labeled 'target' in green": `addReferenceLine {field: {fieldName: "Sales", objectName: "Orders", function: "Avg"}, line: {valueType: "Value", value: 100, labelType: "Custom", customLabel: "target"}, lineColor: "#00FF00"}`
265
+ - "add a sum line for Sales per pane": `addReferenceLine {field: {fieldName: "Sales", objectName: "Orders"}, line: {valueType: "Computation", function: "Sum"}, scope: "Pane"}`
266
+ - "add an average line on Sales computed using sum of Discount": `addReferenceLine {field: {...Sales}, line: {valueType: "Computation", function: "Avg", computationField: {fieldName: "Discount", objectName: "Orders", function: "Sum"}}}`
267
+ - "make the Sales reference line red" (color only, omit `line`): `editReferenceLine {field: {...Sales}, lineColor: "#FF0000"}`
268
+ - "remove the 2nd reference line on avg of Sales": `removeReferenceLine {field: {fieldName: "Sales", objectName: "Orders", function: "Avg"}, lineOccurrence: 2}`
269
+ - "update the 2nd reference line on the 1st Sum of Sales to a max line" (BOTH selectors): `editReferenceLine {field: {fieldName: "Sales", objectName: "Orders", function: "Sum"}, occurrence: 1, lineOccurrence: 2, line: {valueType: "Computation", function: "Max"}}`
270
+
271
+ ## 8. Table-layout shelf operations (Columns/Groups)
272
+
273
+ TABLE-layout visualizations use two shelves — **Columns** (fields shown as table
274
+ columns) and **Groups** (fields those columns are grouped by) — instead of
275
+ rows/columns. These operations are the table equivalent of §2's shelf-field row and
276
+ **do not apply to a VizQL chart** — convert to Table first with `changeLayout` (§10),
277
+ or these error. `shelfType` names the shelf: `"columns"` or `"groups"`; the target
278
+ field nests under a `"field"` object exactly like `addFieldToRowsOrColumns`, using
279
+ the same canonical `function` values.
280
+
281
+ - `addFieldToGroupsOrColumns` — `{field, shelfType, insertPosition?}` (0-based;
282
+ appended when omitted).
283
+ - `removeFieldFromGroupsOrColumns` — `{field, shelfType, occurrence?}` (1-based,
284
+ picks the copy).
285
+ - `replaceFieldFromGroupsOrColumns` — `{existingField, newField, shelfType,
286
+ occurrence?}`, keeping the replaced field's position.
287
+ - `reorderFieldsOnGroupsOrColumns` — `{field, shelfType, insertPosition, occurrence?}`
288
+ — moves a field already on the shelf; adds/removes nothing.
289
+ - `changeFieldOnGroupsOrColumns` — changes an EXISTING table-shelf field's
290
+ aggregation and/or role in place: `{field, shelfType, occurrence?, function?,
291
+ role?}` — supply at least one of `function` (an aggregation) or `role`
292
+ (`"Dimension"`/`"Measure"`); the omitted one is unchanged. Changing to a Measure
293
+ requires `function` (`Sum`/`Avg`/`Median`/`Min`/`Max`/`Count`/`CountD`/`Stdev`/
294
+ `Stdevp`/`Var`/`Varp`); changing to a Dimension drops the function. **No
295
+ `displayCategory` param** (unlike `changeFieldOnRowsOrColumns`) — table fields are
296
+ always discrete.
297
+
298
+ Worked examples:
299
+
300
+ - "Add Region to the table columns": `addFieldToGroupsOrColumns {field: {fieldName: "Region", objectName: "Orders"}, shelfType: "columns"}`
301
+ - "Group the table by Category": `addFieldToGroupsOrColumns {field: {fieldName: "Category", objectName: "Orders"}, shelfType: "groups"}`
302
+ - "Add total Sales as the first table column": `addFieldToGroupsOrColumns {field: {fieldName: "Sales", objectName: "Orders", function: "Sum"}, shelfType: "columns", insertPosition: 0}`
303
+ - "Remove Sales from the table columns": `removeFieldFromGroupsOrColumns {field: {fieldName: "Sales", objectName: "Orders", function: "Sum"}, shelfType: "columns"}`
304
+ - "Swap Category for Segment in the table groups": `replaceFieldFromGroupsOrColumns {existingField: {fieldName: "Category", objectName: "Orders"}, newField: {fieldName: "Segment", objectName: "Orders"}, shelfType: "groups"}`
305
+ - "Move Sales to the first table column": `reorderFieldsOnGroupsOrColumns {field: {fieldName: "Sales", objectName: "Orders"}, shelfType: "columns", insertPosition: 0}`
306
+ - "Average Sales in the table instead of summing it" (Sum of Sales on columns): `changeFieldOnGroupsOrColumns {field: {fieldName: "Sales", objectName: "Orders", function: "Sum"}, shelfType: "columns", function: "Avg"}`
307
+ - "Aggregate the Category column as a count of distinct values": `changeFieldOnGroupsOrColumns {field: {fieldName: "Category", objectName: "Orders"}, shelfType: "columns", role: "Measure", function: "CountD"}`
308
+
309
+ ## 9. Table totals and subtotals
310
+
311
+ TABLE layout only. Two separate summary rows — **do not confuse them**:
312
+
313
+ - **Grand totals** (`showGrandTotals`, `editGrandTotalsLabel`) — ONE row summarizing
314
+ the whole table. Pick this for "total"/"grand total"/"total row"/"summary row" with
315
+ no mention of groups.
316
+ - **Subtotals** (`showSubtotals`, `editSubtotalsLabel`) — one row PER GROUP boundary.
317
+ Pick this for "subtotal"/"group total"/"total for each group"/"totals per category".
318
+
319
+ Both toggle ROW flags only — there is no `columns` parameter; column subtotals are
320
+ never legal (`showSubtotals` rejects `columns: true`), and `showGrandTotals` preserves
321
+ an existing column-grand-total flag but does not toggle it.
322
+
323
+ - `isVisible` (required) shows (`true`) or hides (`false`) the row.
324
+ - `position` (optional): `"Start"` (above the table, or above each group) or `"End"`
325
+ (below) — map "top"/"above"/"first" → `"Start"`, "bottom"/"below"/"last"/"at the
326
+ end" → `"End"`. Omit when unsaid — grand totals default to `"End"`; subtotals
327
+ follow the grand totals. **`position` is rewritten on EVERY call** — when only
328
+ hiding a row, pass its current position to keep where it sits.
329
+ - `editGrandTotalsLabel`/`editSubtotalsLabel` rename the row (`{label: "text"}`;
330
+ defaults read "Grand Total"/"Subtotal"). **To reset to the default** — "reset it",
331
+ "clear the name", "remove the custom label" — send **EMPTY params `{}`** and leave
332
+ `label` out entirely; **NEVER send `{label: ""}`** (an empty or all-whitespace
333
+ label is rejected). These only rename a row the table already has (shown or
334
+ hidden), so emit the rename ALONE — add a `showGrandTotals`/`showSubtotals` first
335
+ only when the table has no such row yet.
336
+ - **Don't reach for these just because "total" appears** — they add/rename a summary
337
+ ROW only, never a column or a per-row figure: a measure to display/aggregate →
338
+ `addFieldToGroupsOrColumns` (or `addFieldToRowsOrColumns`) with `function: "Sum"`;
339
+ a per-row calc (running total, percent of total, rank, moving average) →
340
+ `applyTableCalc` (§13); a totals COLUMN → `addFieldToGroupsOrColumns`; renaming the
341
+ viz → `changeLabel`.
342
+
343
+ Worked examples:
344
+
345
+ - "Add a grand total row to this table": `showGrandTotals {isVisible: true}`
346
+ - "Show totals at the top of the table": `showGrandTotals {isVisible: true, position: "Start"}`
347
+ - "Turn off the total row": `showGrandTotals {isVisible: false}`
348
+ - "Rename the grand total row to 'Overall Total'": `editGrandTotalsLabel {label: "Overall Total"}`
349
+ - "Reset the grand total label to the default": `editGrandTotalsLabel {}`
350
+ - "Show subtotals for each group": `showSubtotals {isVisible: true}`
351
+ - "Add group totals at the top of each group": `showSubtotals {isVisible: true, position: "Start"}`
352
+ - "Rename the subtotal label to 'Group Total'": `editSubtotalsLabel {label: "Group Total"}`
353
+ - "Clear the subtotal label" (a reset — params stay empty, NOT `{label: ""}`): `editSubtotalsLabel {}`
354
+
355
+ ## 10. Layout and fit
356
+
357
+ - **`changeLayout`** switches the WHOLE visualization between the `Vizql` (chart) and
358
+ `Table` layouts, migrating placed fields onto the target layout's shelves. Unlike
359
+ `changeMarkType` (mark shape only), this changes the layout itself. `targetLayout`
360
+ is a canonical value: `"Vizql"` or `"Table"` — nothing else (a donut/pie is the
361
+ Radial layout and a sankey the Flow layout; this operation cannot switch to either).
362
+ Pick by what the user wants to see: a bar/line/area/scatter graph → `"Vizql"`; a
363
+ text table → `"Table"`.
364
+ - "Turn this into a table" → `{targetLayout: "Table"}`
365
+ - "Make this a bar chart again" → `{targetLayout: "Vizql"}`
366
+ - **`changeVisualizationFit`** sets how the viz scales to fit its container: `{fit:
367
+ "Standard"|"Entire"|"RowHeadersWidth"}`. `"Standard"` is default sizing (VizQL and
368
+ Table); `"Entire"` fills the whole view (VizQL only); `"RowHeadersWidth"` fits the
369
+ row-header width (Table only).
370
+ - "Make this chart fit the entire view" → `{fit: "Entire"}`
371
+ - "Fit the table to the row headers" → `{fit: "RowHeadersWidth"}`
372
+
373
+ Chaining example: "Turn this into a table and add a grand total row" →
374
+ `changeLayout {targetLayout: "Table"}`, then `showGrandTotals {isVisible: true}` (fields
375
+ must land on the table's shelves before any table-specific operation references them).
376
+
377
+ ## 11. Field label, field-label visibility, axis visibility, number/date format
378
+
379
+ Four distinct single-field cosmetic operations — do not conflate them with
380
+ `changeLabel` (renames the visualization itself, §2) or `editAxisTitle`/
381
+ `resetAxisTitle` (sets a custom axis title text, independent of the field's own
382
+ label).
383
+
384
+ - **`editFieldLabel`** renames a field's displayed CAPTION — wherever the field's
385
+ name appears (axis, row/column header, table header): `{field, label,
386
+ occurrence?}`. Pass `label` to rename; an empty string reverts to the default
387
+ name. Works on any layout.
388
+ - "Rename the Sales axis to Revenue" → `{field: {fieldName: "Sales", objectName: "Orders", function: "Sum"}, label: "Revenue"}`
389
+ - "Reset the Sales label" → `{field: {...Sales}, label: ""}`
390
+ - **`showFieldLabel`** shows/hides the field labels on the WHOLE SHELF a field sits
391
+ on (affects every field on that shelf, not just the one named): `{field,
392
+ isVisible, occurrence?}`. Only supported on Rows, Columns, Groups, and Levels
393
+ shelves (VizQL/Table/Flow), and only for DISCRETE fields (a discrete measure can be
394
+ toggled; a continuous field errors).
395
+ - "Hide the row field labels" → `{field: {fieldName: "Region", objectName: "Orders"}, isVisible: false}`
396
+ - **`showAxis`** shows/hides the axis for one CONTINUOUS field on the Rows or Columns
397
+ shelf: `{field, isVisible, occurrence?}`.
398
+ - "Hide the Sales axis" → `{field: {fieldName: "Sales", objectName: "Orders", function: "Sum"}, isVisible: false}`
399
+ - **`setFieldFormat`** changes how a field's VALUES are displayed — not its
400
+ aggregation/role/position (`changeFieldOnRowsOrColumns`) or its axis title
401
+ (`editAxisTitle`). Target is keyed **`targetField`** (`{fieldName, objectName?,
402
+ function?}`) and must already be on a shelf or mark encoding; every copy of the
403
+ field is reformatted together.
404
+ - Number format: `format: {type: "Number"|"Percentage"|"Currency"|"NumberShort"|
405
+ "PercentageShort"|"CurrencyShort" (the "Short" forms abbreviate to K/M/B),
406
+ decimalPlaces? (0 for whole numbers, 2 for cents), displayUnits? ("Auto"|"None"|
407
+ "K"|"M"|"B"|"G"), prefix?, suffix?, negativeValuesFormat? ("Auto"|"Parenthesis"|
408
+ "Minus"), includeThousandSeparator?}`.
409
+ - Date format: `format: {dateTemplate: "MM/dd/yyyy"|"yyyy-MM-dd"|...}`. A
410
+ date/datetime field takes only a `dateTemplate`; a non-date field only a number
411
+ format (mixing errors); a date MEASURE accepts a `dateTemplate` only when
412
+ aggregated with `Min`, `Max`, or `Attr`.
413
+ - "Show Sales as currency" → `{targetField: {fieldName: "Sales", objectName: "Orders", function: "Sum"}, format: {type: "Currency"}}`
414
+ - "Format Discount as a percent with 1 decimal place" → `{targetField: {fieldName: "Discount", objectName: "Orders"}, format: {type: "Percentage", decimalPlaces: 1}}`
415
+ - "Abbreviate Sales to millions" → `{targetField: {...Sales}, format: {type: "NumberShort", displayUnits: "M"}}`
416
+ - "Show Order Date as MM/DD/YYYY" → `{targetField: {fieldName: "Order_Date", objectName: "Orders"}, format: {dateTemplate: "MM/dd/yyyy"}}`
417
+
418
+ ## 12. Measure Values shelf editing
419
+
420
+ The Measure Values shelf holds a list of measures pivoted by a Measure Names field on
421
+ rows or columns so multiple measures share one axis (e.g. "show Sum of Amount and
422
+ Count on the same axis"). VizQL only. **Creating** the construct on a brand-new,
423
+ empty visualization is covered in `viz-authoring.md` (add the special Measure Names/
424
+ Measure Values field via `addFieldToRowsOrColumns` with `{field: {type:
425
+ "MeasureNames"|"MeasureValues"}}` — only valid before any field is placed). Once the
426
+ construct exists, EDIT it with these operations:
427
+
428
+ - `addFieldToMeasureValues` — `{field, insertPosition?}` (appends when omitted; only
429
+ measures — dimensions error).
430
+ - `reorderFieldsOnMeasureValues` — `{field, insertPosition, occurrence?}` (moves a
431
+ measure already on the shelf).
432
+ - `replaceFieldFromMeasureValues` — `{existingField, newField, occurrence?}` — swaps a
433
+ measure already on the shelf for one that is not, keeping the replaced measure's
434
+ slot.
435
+ - `removeFieldFromMeasureValues` — `{field, occurrence?}`.
436
+ - The same measure can sit on the shelf more than once — `occurrence` (1-based)
437
+ picks which copy.
438
+
439
+ Worked examples:
440
+
441
+ - "Show Sum of Sales and Profit on the same axis" (Sum of Sales already on an axis, add Profit): `addFieldToMeasureValues {field: {fieldName: "Profit", objectName: "Orders", function: "Sum"}}`
442
+ - "Add Profit at position 0 on Measure Values": `addFieldToMeasureValues {field: {fieldName: "Profit", objectName: "Orders", function: "Sum"}, insertPosition: 0}`
443
+ - "Move Sales to position 1 on Measure Values": `reorderFieldsOnMeasureValues {field: {fieldName: "Sales", objectName: "Orders", function: "Sum"}, insertPosition: 1}`
444
+ - "Replace Profit with Quantity on Measure Values" (Profit on the shelf, Quantity not): `replaceFieldFromMeasureValues {existingField: {fieldName: "Profit", objectName: "Orders", function: "Sum"}, newField: {fieldName: "Quantity", objectName: "Orders", function: "Sum"}}`
445
+ - "Remove the second Sum of Sales from Measure Values": `removeFieldFromMeasureValues {field: {fieldName: "Sales", objectName: "Orders", function: "Sum"}, occurrence: 2}`
446
+
447
+ ## 13. Table calcs — `quickTableCalc` types and `computeUsing`
448
+
449
+ `applyTableCalc`/`editTableCalc`/`removeTableCalc` target a measure already on a
450
+ shelf. `applyTableCalc` adds; `editTableCalc` changes (errors if the field has no
451
+ existing calc); `removeTableCalc` clears (errors if none).
452
+
453
+ `quickTableCalc.type` — canonical values: `RunningSum`, `RunningAvg`, `RunningMin`,
454
+ `RunningMax`, `Difference`, `PercentDifference`, `PercentOfTotal`, `Rank`,
455
+ `RankModified`, `RankDense`, `RankUnique`, `Percentile`, `MovingSum`, `MovingAverage`,
456
+ `MovingMin`, `MovingMax`.
457
+
458
+ - **Moving types** (`MovingSum`/`MovingAverage`/`MovingMin`/`MovingMax`) accept
459
+ `previousValues`, `nextValues`, `useCurrentValue`. `MovingAverage` defaults to
460
+ `previousValues: 2, nextValues: 0, useCurrentValue: true`; the other moving types
461
+ have no defaults — supply them explicitly.
462
+ - **Ranking types** (`Rank`/`RankModified`/`RankDense`/`RankUnique`) accept `order`
463
+ (`"Ascending"`/`"Descending"`). `Rank` defaults to `"Descending"`; the other
464
+ ranking types have no default.
465
+
466
+ `computeUsing` (optional on `applyTableCalc`/`editTableCalc`) — `{type,
467
+ useOrderByForExtraFields?}`. `type` is the calc's addressing direction:
468
+ `TableAcross`, `TableDown`, `TableAcrossThenDown`, `TableDownThenAcross`, `PaneAcross`,
469
+ `PaneDown`, `PaneAcrossThenDown`, `PaneDownThenAcross`, `Cell`. `useOrderByForExtraFields`
470
+ (boolean) orders the extra (non-addressing) fields by whatever sort is applied to
471
+ them.
472
+
473
+ Worked examples:
474
+
475
+ - "show Sales as a running total": `applyTableCalc {field: {fieldName: "Sales", objectName: "Orders"}, quickTableCalc: {type: "RunningSum"}}`
476
+ - "show a 3-period moving average of Sales": `applyTableCalc {field: {...Sales}, quickTableCalc: {type: "MovingAverage", previousValues: 2, nextValues: 0, useCurrentValue: true}}`
477
+ - "compute the running total down the table instead": `editTableCalc {field: {...Sales}, quickTableCalc: {type: "RunningSum"}, computeUsing: {type: "TableDown"}}`
478
+ - "remove the table calculation from Sales": `removeTableCalc {field: {...Sales}}`
479
+
480
+ ## 14. Logical views and HLV field naming
481
+
482
+ When a field lives under `semanticLogicalViews[].semanticUnions[].
483
+ semanticMappedFields[]`, use the **logical view's** apiName as `objectName` — never
484
+ the union's; the logical view is the queryable object (e.g. field "Full_Name20" in
485
+ `Manager_Union44` → `objectName: "Manager_oh"`). Hierarchical logical views (HLVs)
486
+ additionally expose:
487
+
488
+ - **Me/My-team filters** via two hardcoded field names: `SemanticManagerUserIdPath`
489
+ (manager hierarchy) / `SemanticRoleUserIdPath` (role hierarchy) — "Me" uses
490
+ `EndsWithIgnoreCase`, "My team" uses `ContainsIgnoreCase`, both against
491
+ `{expression: USERID18()}`. Me example: `{filter: {fields: [{model:
492
+ "Manager_oh.SemanticManagerUserIdPath"}], operator: "EndsWithIgnoreCase", values:
493
+ ["{expression: USERID18()}"]}, bucket: "filter"}` (My team: same with
494
+ `ContainsIgnoreCase`).
495
+ - **Level-based display** via `hierarchyLevel` on a field object — e.g.
496
+ `{fieldName: "Full_Name", hierarchyLevel: 1, objectName: "Manager_oh",
497
+ displayCategory: "Discrete", role: "Dimension", type: "Field"}`.
498
+ - **Per-level filters** via `ANCESTOR_AT_LEVEL(n, [Object].[Field])` expressions —
499
+ e.g. `{expression: "ANCESTOR_AT_LEVEL(1, [Manager_oh].[Full_Name])", operator:
500
+ "In", values: ["Sarah Chen (CEO)"]}`.
501
+
502
+ ## 15. Persistence handoff — `update_visualization`
503
+
504
+ `edit_visualization` never saves — every edit (or chain of edits) is a preview only.
505
+ **Never call `update_visualization` as an automatic follow-up.** After every
506
+ successful `edit_visualization` call — even small or chained edits — your very next
507
+ reply MUST end by asking, verbatim: **"Do you want to save the visualization?"**
508
+ Call `update_visualization` **only** on confirmation given *after seeing that
509
+ preview* (e.g. "yes", "save it") — this applies even when the
510
+ original request already said "save it" in the same message as the edit; that
511
+ upfront wording does not substitute for a confirmation given after the preview
512
+ renders. See `tasks/edit-viz.md` for the full flow.
513
+
514
+ **`update_visualization` takes only `visualizationId` — never pass a `visualizationMetadata`
515
+ body.** The server tracks the latest working definition for that id server-side (whatever
516
+ `edit_visualization` last produced for it) and persists that; there's nothing else to
517
+ pass. It does not accept or need the workspace/SDM info — those are already fixed on the
518
+ existing visualization record. Its response is just `{updateStatus: "SUCCESS"}` — no
519
+ metadata, no id.
520
+
521
+ **Update is in-place only — it does not relocate the viz.** It writes back over the
522
+ same visualization (by `visualizationId`), so it updates rather than creates a copy,
523
+ and it does NOT move the viz to another workspace or re-point it at a different
524
+ semantic model.
525
+
526
+ **Round-trips back into `edit_visualization` without a re-fetch.** You already have the
527
+ `visualizationId` from before calling `update_visualization` — reuse that same id
528
+ directly in another `edit_visualization` call (§1) without a `get_visualization`
529
+ round trip; the server's working state for that id already reflects the saved edits.