From b4885c900dee1e48a29b3e4298adf7689d338fa9 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 10:34:19 +0100 Subject: [PATCH 1/5] Add a skill to migrate dashboards Allows author to create a new dashboard using tiles from an LCP, and then migrate it back to LCP format --- .claude/skills/migrate-dashboard/SKILL.md | 107 +++++++++ .../references/migrated-example.json | 216 ++++++++++++++++++ .../references/source-example.json | 210 +++++++++++++++++ .../migrate-dashboard/scripts/normalize.js | 116 ++++++++++ 4 files changed, 649 insertions(+) create mode 100644 .claude/skills/migrate-dashboard/SKILL.md create mode 100644 .claude/skills/migrate-dashboard/references/migrated-example.json create mode 100644 .claude/skills/migrate-dashboard/references/source-example.json create mode 100644 .claude/skills/migrate-dashboard/scripts/normalize.js diff --git a/.claude/skills/migrate-dashboard/SKILL.md b/.claude/skills/migrate-dashboard/SKILL.md new file mode 100644 index 00000000..ac07dd79 --- /dev/null +++ b/.claude/skills/migrate-dashboard/SKILL.md @@ -0,0 +1,107 @@ +--- +name: migrate-dashboard +description: Migrate an exported platform dashboard JSON to plugin format. Use when the user pastes a dashboard JSON to convert, says "migrate this dashboard", or wants to add an exported dashboard to a plugin as default content. +metadata: + author: SquaredUp + version: "0.0.1" +--- + +# Migrate Dashboard to Plugin Format + +Convert a platform-exported dashboard JSON to plugin format by normalizing hardcoded IDs to template variables, then saving as plugin default content. + +**Announce at start:** "I'm using the migrate-dashboard skill." + +--- + +## Steps + +### 1. Locate plugin + +Identify the plugin and version from context, or ask the user. + +Working folder: `plugins///` + +If only one version exists, use it without asking. + +**Done when:** working folder is set. + +--- + +### 2. Receive dashboard JSON + +Ask the user to paste the exported dashboard JSON if not already provided. + +**Done when:** valid JSON with `"_type": "layout/grid"` is in hand. + +--- + +### 3. Normalize + +Run the normalization script to replace all hardcoded platform values with template variables: + +```bash +node .claude/skills/migrate-dashboard/scripts/normalize.js [plugins///defaultContent/scopes.json] [--scope-name "Scope Name"] +``` + +Pass `scopes.json` when the dashboard has tiles with `config.variables` or `config.scope` (perspective dashboard). The script reads `config.dataStream.name` from each tile to generate the correct `{{dataStreams.[name]}}` reference. + +If `scopes.json` has multiple entries, read it first and determine which scope applies. It may be obvious from the dashboard content (e.g. tile titles referencing "Agent" or "Organization"), but if not, ask the user before running the script. Pass the chosen scope via `--scope-name "Scope Name"`. Run once per scope if the dashboard mixes tiles from multiple scopes. + +Verify the output — check that: +- No `config-*` IDs remain (all → `{{configId}}`) +- No `space-*` IDs remain (all → `{{workspaceId}}`) +- All `config.dataStream.id` values are `{{dataStreams.[name]}}` form +- All `config.activePluginConfigIds` are `["{{configId}}"]` +- `config.scopes[]` entries with `ids_defaultScopeIds` bindings are removed +- Perspective tiles: `config.variables`, `config.scope.variable`, `config.scope.scope` are templatized + +**Done when:** no hardcoded platform IDs remain in any tile. + +--- + +### 4. Confirm title and timeframe + +Ask the user to confirm the dashboard title (suggest one derived from the content). + +Ask for timeframe. Default: `last24hours`. Options: + +`last1hour` · `last12hours` · `last24hours` · `last30days` · `thisMonth` · `thisQuarter` · `thisYear` · `lastMonth` · `lastQuarter` · `lastYear` · `none` + +**Done when:** title confirmed and timeframe chosen. + +--- + +### 5. Save + +Write to `plugins///defaultContent/.dash.json`: + +```json +{ + "name": "", + "schemaVersion": "1.5", + "timeframe": "", + "variables": [], + "dashboard": +} +``` + +For a perspective dashboard, set `"variables"` to the variable template from scopes.json, e.g.: + +```json +"variables": ["{{variables.[Algolia Index]}}"] +``` + +For a non-perspective dashboard, leave `"variables": []`. + +See [source-example.json](references/source-example.json) and [migrated-example.json](references/migrated-example.json) for a before/after reference. + +**Done when:** file written. + +--- + +### 6. Update manifest + +Ask where in `manifest.json` this dashboard should appear. Update `manifest.json` with the new entry at the specified position. + +**Done when:** `manifest.json` updated and order confirmed. diff --git a/.claude/skills/migrate-dashboard/references/migrated-example.json b/.claude/skills/migrate-dashboard/references/migrated-example.json new file mode 100644 index 00000000..3fc2edcb --- /dev/null +++ b/.claude/skills/migrate-dashboard/references/migrated-example.json @@ -0,0 +1,216 @@ +{ + "name": "Algolia Index", + "schemaVersion": "1.5", + "timeframe": "last24hours", + "variables": ["{{variables.[Algolia Index]}}"], + "dashboard": { + "_type": "layout/grid", + "version": 1, + "contents": [ + { + "static": false, + "w": 4, + "moved": false, + "x": 0, + "h": 4, + "i": "9b9fc2b9-c56d-47de-a4c4-d151e467a210", + "y": 6, + "z": 0, + "config": { + "timeframe": "none", + "variables": ["{{variables.[Algolia Index]}}"], + "dataStream": { + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[properties]}}" + }, + "scope": { + "variable": "{{variables.[Algolia Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Algolia Indices]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Index Details", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "x": 0, + "h": 4, + "i": "dfa41039-1657-4142-b6a5-16ac7abffb9d", + "y": 4, + "z": 0, + "config": { + "variables": ["{{variables.[Algolia Index]}}"], + "dataStream": { + "name": "searchCount", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[searchCount]}}", + "sort": { "by": [["date_byDay", "asc"]] }, + "group": { + "by": [["date", "byDay"]], + "aggregate": [{ "type": "sum", "names": ["count"] }] + } + }, + "scope": { + "variable": "{{variables.[Algolia Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Algolia Indices]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Search Volume", + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "yAxisLabel": "Searches", + "xAxisColumn": "date_byDay", + "showLegend": false, + "showYAxisLabel": true, + "seriesColumn": "none", + "showTrendLine": false, + "legendPosition": "bottom", + "yAxisColumn": ["count_sum"] + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "x": 2, + "h": 4, + "i": "09cc7897-4e96-44b3-9205-144fcf3ef5fb", + "y": 4, + "z": 0, + "config": { + "variables": ["{{variables.[Algolia Index]}}"], + "dataStream": { + "name": "noResultRate", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[noResultRate]}}", + "sort": { "by": [["date_byDay", "asc"]] }, + "group": { + "by": [["date", "byDay"]], + "aggregate": [{ "type": "mean", "names": ["rate"] }] + } + }, + "scope": { + "variable": "{{variables.[Algolia Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Algolia Indices]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "No-Result Rate", + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "yAxisLabel": "No-Result Rate", + "xAxisColumn": "date_byDay", + "showLegend": false, + "showYAxisLabel": true, + "seriesColumn": "none", + "showTrendLine": false, + "legendPosition": "bottom", + "yAxisColumn": ["rate_mean"] + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "x": 0, + "h": 4, + "i": "1c816936-aafc-42dc-8096-10f0281a6e17", + "y": 0, + "z": 0, + "config": { + "variables": ["{{variables.[Algolia Index]}}"], + "dataStream": { + "name": "topSearches", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[topSearches]}}", + "sort": { "by": [["count", "desc"]] } + }, + "scope": { + "variable": "{{variables.[Algolia Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Algolia Indices]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Top Searches", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": ["search", "count", "nbHits"], + "transpose": false + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "x": 2, + "h": 4, + "i": "1727a100-9c88-41cb-b157-c0f197b1b13e", + "y": 0, + "z": 0, + "config": { + "variables": ["{{variables.[Algolia Index]}}"], + "dataStream": { + "name": "noResultSearches", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[noResultSearches]}}", + "sort": { "by": [["count", "desc"]] } + }, + "scope": { + "variable": "{{variables.[Algolia Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Algolia Indices]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Searches With No Results", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": ["search", "count", "withFilterCount"], + "transpose": false + } + } + } + } + } + ], + "columns": 4 + } +} diff --git a/.claude/skills/migrate-dashboard/references/source-example.json b/.claude/skills/migrate-dashboard/references/source-example.json new file mode 100644 index 00000000..7dd8c0a5 --- /dev/null +++ b/.claude/skills/migrate-dashboard/references/source-example.json @@ -0,0 +1,210 @@ +{ + "_type": "layout/grid", + "version": 1, + "contents": [ + { + "static": false, + "w": 4, + "moved": false, + "x": 0, + "h": 4, + "i": "9b9fc2b9-c56d-47de-a4c4-d151e467a210", + "y": 6, + "z": 0, + "config": { + "timeframe": "none", + "variables": ["var-J4pfoHDhrn2MRUQuPqv4"], + "dataStream": { + "pluginConfigId": "config-6PK2neo9yFh0rx3z80V2", + "id": "datastream-properties" + }, + "scope": { + "variable": "var-J4pfoHDhrn2MRUQuPqv4", + "workspace": "space-m86qNQgA8Mm39fbrpiNX", + "scope": "scope-mFB0VLVFCzanCpmI07nA" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["config-6PK2neo9yFh0rx3z80V2"], + "title": "Index Details", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "x": 0, + "h": 4, + "i": "dfa41039-1657-4142-b6a5-16ac7abffb9d", + "y": 4, + "z": 0, + "config": { + "variables": ["var-J4pfoHDhrn2MRUQuPqv4"], + "dataStream": { + "name": "searchCount", + "pluginConfigId": "config-6PK2neo9yFh0rx3z80V2", + "id": "datastream-GECbenNA3dhoyzEmkVrW", + "sort": { "by": [["date_byDay", "asc"]] }, + "group": { + "by": [["date", "byDay"]], + "aggregate": [{ "type": "sum", "names": ["count"] }] + } + }, + "scope": { + "variable": "var-J4pfoHDhrn2MRUQuPqv4", + "workspace": "space-m86qNQgA8Mm39fbrpiNX", + "scope": "scope-mFB0VLVFCzanCpmI07nA" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["config-6PK2neo9yFh0rx3z80V2"], + "title": "Search Volume", + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "yAxisLabel": "Searches", + "xAxisColumn": "date_byDay", + "showLegend": false, + "showYAxisLabel": true, + "seriesColumn": "none", + "showTrendLine": false, + "legendPosition": "bottom", + "yAxisColumn": ["count_sum"] + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "x": 2, + "h": 4, + "i": "09cc7897-4e96-44b3-9205-144fcf3ef5fb", + "y": 4, + "z": 0, + "config": { + "variables": ["var-J4pfoHDhrn2MRUQuPqv4"], + "dataStream": { + "name": "noResultRate", + "pluginConfigId": "config-6PK2neo9yFh0rx3z80V2", + "id": "datastream-WZlGFhDNuEpIX3axqpcR", + "sort": { "by": [["date_byDay", "asc"]] }, + "group": { + "by": [["date", "byDay"]], + "aggregate": [{ "type": "mean", "names": ["rate"] }] + } + }, + "scope": { + "variable": "var-J4pfoHDhrn2MRUQuPqv4", + "workspace": "space-m86qNQgA8Mm39fbrpiNX", + "scope": "scope-mFB0VLVFCzanCpmI07nA" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["config-6PK2neo9yFh0rx3z80V2"], + "title": "No-Result Rate", + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "yAxisLabel": "No-Result Rate", + "xAxisColumn": "date_byDay", + "showLegend": false, + "showYAxisLabel": true, + "seriesColumn": "none", + "showTrendLine": false, + "legendPosition": "bottom", + "yAxisColumn": ["rate_mean"] + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "x": 0, + "h": 4, + "i": "1c816936-aafc-42dc-8096-10f0281a6e17", + "y": 0, + "z": 0, + "config": { + "variables": ["var-J4pfoHDhrn2MRUQuPqv4"], + "dataStream": { + "name": "topSearches", + "pluginConfigId": "config-6PK2neo9yFh0rx3z80V2", + "id": "datastream-f7fhFvXjv82JKG5Np0lc", + "sort": { "by": [["count", "desc"]] } + }, + "scope": { + "variable": "var-J4pfoHDhrn2MRUQuPqv4", + "workspace": "space-m86qNQgA8Mm39fbrpiNX", + "scope": "scope-mFB0VLVFCzanCpmI07nA" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["config-6PK2neo9yFh0rx3z80V2"], + "title": "Top Searches", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": ["search", "count", "nbHits"], + "transpose": false + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "x": 2, + "h": 4, + "i": "1727a100-9c88-41cb-b157-c0f197b1b13e", + "y": 0, + "z": 0, + "config": { + "variables": ["var-J4pfoHDhrn2MRUQuPqv4"], + "dataStream": { + "name": "noResultSearches", + "pluginConfigId": "config-6PK2neo9yFh0rx3z80V2", + "id": "datastream-Y85PbGnycRrMZ6eH6bSw", + "sort": { "by": [["count", "desc"]] } + }, + "scope": { + "variable": "var-J4pfoHDhrn2MRUQuPqv4", + "workspace": "space-m86qNQgA8Mm39fbrpiNX", + "scope": "scope-mFB0VLVFCzanCpmI07nA" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["config-6PK2neo9yFh0rx3z80V2"], + "title": "Searches With No Results", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": ["search", "count", "withFilterCount"], + "transpose": false + } + } + } + } + } + ], + "columns": 4 +} diff --git a/.claude/skills/migrate-dashboard/scripts/normalize.js b/.claude/skills/migrate-dashboard/scripts/normalize.js new file mode 100644 index 00000000..f1b6ef63 --- /dev/null +++ b/.claude/skills/migrate-dashboard/scripts/normalize.js @@ -0,0 +1,116 @@ +#!/usr/bin/env node +// Usage: node normalize.js [scopes.json] [--scope-name "Scope Name"] +// Writes normalized dashboard JSON to stdout. +// Pass scopes.json for perspective dashboards (those with config.variables or config.scope). +// If scopes.json has multiple entries, --scope-name is required to select which one to apply. + +const fs = require('fs'); + +const args = process.argv.slice(2); +const scopeNameFlag = args.indexOf('--scope-name'); +const requestedScopeName = scopeNameFlag !== -1 ? args[scopeNameFlag + 1] : null; +const positional = args.filter((a, i) => { + if (a.startsWith('--')) return false; + if (scopeNameFlag !== -1 && i === scopeNameFlag + 1) return false; + return true; +}); + +const dashboardPath = positional[0]; +const scopesPath = positional[1]; + +if (!dashboardPath) { + process.stderr.write( + 'Usage: node normalize.js [scopes.json] [--scope-name "Scope Name"]\n' + ); + process.exit(1); +} + +const dashboard = JSON.parse(fs.readFileSync(dashboardPath, 'utf8')); +const allScopes = scopesPath ? JSON.parse(fs.readFileSync(scopesPath, 'utf8')) : null; + +let scopeEntry = null; +if (allScopes) { + if (allScopes.length === 1) { + scopeEntry = allScopes[0]; + } else if (requestedScopeName) { + scopeEntry = allScopes.find(s => s.name === requestedScopeName); + if (!scopeEntry) { + const available = allScopes.map(s => ` "${s.name}"`).join('\n'); + process.stderr.write( + `No scope named "${requestedScopeName}". Available:\n${available}\n` + ); + process.exit(1); + } + } else { + const available = allScopes.map(s => ` "${s.name}"`).join('\n'); + process.stderr.write( + `scopes.json has multiple entries — use --scope-name to select one:\n${available}\n` + ); + process.exit(1); + } +} + +const scopeName = scopeEntry?.name; +const variableName = scopeEntry?.variable?.name; + +function normalizeTile(tile) { + const config = structuredClone(tile.config); + if (!config) return tile; + + // Remove scope entries that pin to specific nodes via ids_defaultScopeIds + if (Array.isArray(config.scopes)) { + config.scopes = config.scopes.filter( + s => !(s.bindings && 'ids_defaultScopeIds' in s.bindings) + ); + if (config.scopes.length === 0) delete config.scopes; + } + + // Templatize dataStream + if (config.dataStream) { + if (config.dataStream.name) { + config.dataStream.id = `{{dataStreams.[${config.dataStream.name}]}}`; + } + if (config.dataStream.pluginConfigId) { + config.dataStream.pluginConfigId = '{{configId}}'; + } + } + + if (config.activePluginConfigIds) { + config.activePluginConfigIds = ['{{configId}}']; + } + + // Perspective fields + if (config.variables && variableName) { + config.variables = [`{{variables.[${variableName}]}}`]; + } + + if (config.scope) { + if (config.scope.variable && variableName) { + config.scope.variable = `{{variables.[${variableName}]}}`; + } + if (config.scope.workspace) { + config.scope.workspace = '{{workspaceId}}'; + } + if (config.scope.scope && scopeName) { + config.scope.scope = `{{scopes.[${scopeName}]}}`; + } + } + + return { ...tile, config }; +} + +// String-level pass: catch any remaining hardcoded IDs the structural pass may miss +// (e.g. inside scope.query strings) +function globalReplace(obj) { + let s = JSON.stringify(obj); + s = s.replace(/config-[A-Za-z0-9]+/g, '{{configId}}'); + s = s.replace(/space-[A-Za-z0-9]+/g, '{{workspaceId}}'); + return JSON.parse(s); +} + +const normalized = globalReplace({ + ...dashboard, + contents: (dashboard.contents || []).map(normalizeTile), +}); + +process.stdout.write(JSON.stringify(normalized, null, 2) + '\n'); From 2b650da90efc9cdc292103191e8139a90ec9e781 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Fri, 19 Jun 2026 13:49:43 +0100 Subject: [PATCH 2/5] suggest sonnet model for migration skill --- .claude/skills/migrate-dashboard/SKILL.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.claude/skills/migrate-dashboard/SKILL.md b/.claude/skills/migrate-dashboard/SKILL.md index ac07dd79..bc4ac5dd 100644 --- a/.claude/skills/migrate-dashboard/SKILL.md +++ b/.claude/skills/migrate-dashboard/SKILL.md @@ -1,6 +1,7 @@ --- name: migrate-dashboard description: Migrate an exported platform dashboard JSON to plugin format. Use when the user pastes a dashboard JSON to convert, says "migrate this dashboard", or wants to add an exported dashboard to a plugin as default content. +model: sonnet metadata: author: SquaredUp version: "0.0.1" @@ -49,6 +50,7 @@ Pass `scopes.json` when the dashboard has tiles with `config.variables` or `conf If `scopes.json` has multiple entries, read it first and determine which scope applies. It may be obvious from the dashboard content (e.g. tile titles referencing "Agent" or "Organization"), but if not, ask the user before running the script. Pass the chosen scope via `--scope-name "Scope Name"`. Run once per scope if the dashboard mixes tiles from multiple scopes. Verify the output — check that: + - No `config-*` IDs remain (all → `{{configId}}`) - No `space-*` IDs remain (all → `{{workspaceId}}`) - All `config.dataStream.id` values are `{{dataStreams.[name]}}` form From da8b6744228f05bbffa6f81359d264512356dec9 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Mon, 22 Jun 2026 08:25:09 +0100 Subject: [PATCH 3/5] Ignore datastream-properties during migration and set dashboard version to 1 --- .claude/skills/migrate-dashboard/SKILL.md | 3 +- .../references/migrated-example.json | 430 +++++++++--------- .../references/source-example.json | 400 ++++++++-------- .../migrate-dashboard/scripts/normalize.js | 8 +- 4 files changed, 428 insertions(+), 413 deletions(-) diff --git a/.claude/skills/migrate-dashboard/SKILL.md b/.claude/skills/migrate-dashboard/SKILL.md index bc4ac5dd..bfce6dce 100644 --- a/.claude/skills/migrate-dashboard/SKILL.md +++ b/.claude/skills/migrate-dashboard/SKILL.md @@ -53,8 +53,9 @@ Verify the output — check that: - No `config-*` IDs remain (all → `{{configId}}`) - No `space-*` IDs remain (all → `{{workspaceId}}`) -- All `config.dataStream.id` values are `{{dataStreams.[name]}}` form +- All `config.dataStream.id` values are `{{dataStreams.[name]}}` form, **except** the global built-in `datastream-properties`, which is left as-is - All `config.activePluginConfigIds` are `["{{configId}}"]` +- `dashboard.version` is reset to `1` (the script forces this regardless of the pasted-in version) - `config.scopes[]` entries with `ids_defaultScopeIds` bindings are removed - Perspective tiles: `config.variables`, `config.scope.variable`, `config.scope.scope` are templatized diff --git a/.claude/skills/migrate-dashboard/references/migrated-example.json b/.claude/skills/migrate-dashboard/references/migrated-example.json index 3fc2edcb..c2296612 100644 --- a/.claude/skills/migrate-dashboard/references/migrated-example.json +++ b/.claude/skills/migrate-dashboard/references/migrated-example.json @@ -1,216 +1,220 @@ { - "name": "Algolia Index", - "schemaVersion": "1.5", - "timeframe": "last24hours", - "variables": ["{{variables.[Algolia Index]}}"], - "dashboard": { - "_type": "layout/grid", - "version": 1, - "contents": [ - { - "static": false, - "w": 4, - "moved": false, - "x": 0, - "h": 4, - "i": "9b9fc2b9-c56d-47de-a4c4-d151e467a210", - "y": 6, - "z": 0, - "config": { - "timeframe": "none", - "variables": ["{{variables.[Algolia Index]}}"], - "dataStream": { - "pluginConfigId": "{{configId}}", - "id": "{{dataStreams.[properties]}}" - }, - "scope": { - "variable": "{{variables.[Algolia Index]}}", - "workspace": "{{workspaceId}}", - "scope": "{{scopes.[Algolia Indices]}}" - }, - "_type": "tile/data-stream", - "description": "", - "activePluginConfigIds": ["{{configId}}"], - "title": "Index Details", - "visualisation": { - "type": "data-stream-table", - "config": { - "data-stream-table": { - "transpose": true - } + "name": "Algolia Index", + "schemaVersion": "1.5", + "timeframe": "last24hours", + "variables": ["{{variables.[Algolia Index]}}"], + "dashboard": { + "_type": "layout/grid", + "version": 1, + "contents": [ + { + "static": false, + "w": 4, + "moved": false, + "x": 0, + "h": 4, + "i": "9b9fc2b9-c56d-47de-a4c4-d151e467a210", + "y": 6, + "z": 0, + "config": { + "timeframe": "none", + "variables": ["{{variables.[Algolia Index]}}"], + "dataStream": { + "pluginConfigId": "{{configId}}", + "id": "datastream-properties" + }, + "scope": { + "variable": "{{variables.[Algolia Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Algolia Indices]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Index Details", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "x": 0, + "h": 4, + "i": "dfa41039-1657-4142-b6a5-16ac7abffb9d", + "y": 4, + "z": 0, + "config": { + "variables": ["{{variables.[Algolia Index]}}"], + "dataStream": { + "name": "searchCount", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[searchCount]}}", + "sort": { "by": [["date_byDay", "asc"]] }, + "group": { + "by": [["date", "byDay"]], + "aggregate": [{ "type": "sum", "names": ["count"] }] + } + }, + "scope": { + "variable": "{{variables.[Algolia Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Algolia Indices]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Search Volume", + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "yAxisLabel": "Searches", + "xAxisColumn": "date_byDay", + "showLegend": false, + "showYAxisLabel": true, + "seriesColumn": "none", + "showTrendLine": false, + "legendPosition": "bottom", + "yAxisColumn": ["count_sum"] + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "x": 2, + "h": 4, + "i": "09cc7897-4e96-44b3-9205-144fcf3ef5fb", + "y": 4, + "z": 0, + "config": { + "variables": ["{{variables.[Algolia Index]}}"], + "dataStream": { + "name": "noResultRate", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[noResultRate]}}", + "sort": { "by": [["date_byDay", "asc"]] }, + "group": { + "by": [["date", "byDay"]], + "aggregate": [{ "type": "mean", "names": ["rate"] }] + } + }, + "scope": { + "variable": "{{variables.[Algolia Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Algolia Indices]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "No-Result Rate", + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "yAxisLabel": "No-Result Rate", + "xAxisColumn": "date_byDay", + "showLegend": false, + "showYAxisLabel": true, + "seriesColumn": "none", + "showTrendLine": false, + "legendPosition": "bottom", + "yAxisColumn": ["rate_mean"] + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "x": 0, + "h": 4, + "i": "1c816936-aafc-42dc-8096-10f0281a6e17", + "y": 0, + "z": 0, + "config": { + "variables": ["{{variables.[Algolia Index]}}"], + "dataStream": { + "name": "topSearches", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[topSearches]}}", + "sort": { "by": [["count", "desc"]] } + }, + "scope": { + "variable": "{{variables.[Algolia Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Algolia Indices]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Top Searches", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": ["search", "count", "nbHits"], + "transpose": false + } + } + } + } + }, + { + "static": false, + "w": 2, + "moved": false, + "x": 2, + "h": 4, + "i": "1727a100-9c88-41cb-b157-c0f197b1b13e", + "y": 0, + "z": 0, + "config": { + "variables": ["{{variables.[Algolia Index]}}"], + "dataStream": { + "name": "noResultSearches", + "pluginConfigId": "{{configId}}", + "id": "{{dataStreams.[noResultSearches]}}", + "sort": { "by": [["count", "desc"]] } + }, + "scope": { + "variable": "{{variables.[Algolia Index]}}", + "workspace": "{{workspaceId}}", + "scope": "{{scopes.[Algolia Indices]}}" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["{{configId}}"], + "title": "Searches With No Results", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": [ + "search", + "count", + "withFilterCount" + ], + "transpose": false + } + } + } + } } - } - } - }, - { - "static": false, - "w": 2, - "moved": false, - "x": 0, - "h": 4, - "i": "dfa41039-1657-4142-b6a5-16ac7abffb9d", - "y": 4, - "z": 0, - "config": { - "variables": ["{{variables.[Algolia Index]}}"], - "dataStream": { - "name": "searchCount", - "pluginConfigId": "{{configId}}", - "id": "{{dataStreams.[searchCount]}}", - "sort": { "by": [["date_byDay", "asc"]] }, - "group": { - "by": [["date", "byDay"]], - "aggregate": [{ "type": "sum", "names": ["count"] }] - } - }, - "scope": { - "variable": "{{variables.[Algolia Index]}}", - "workspace": "{{workspaceId}}", - "scope": "{{scopes.[Algolia Indices]}}" - }, - "_type": "tile/data-stream", - "description": "", - "activePluginConfigIds": ["{{configId}}"], - "title": "Search Volume", - "visualisation": { - "type": "data-stream-line-graph", - "config": { - "data-stream-line-graph": { - "yAxisLabel": "Searches", - "xAxisColumn": "date_byDay", - "showLegend": false, - "showYAxisLabel": true, - "seriesColumn": "none", - "showTrendLine": false, - "legendPosition": "bottom", - "yAxisColumn": ["count_sum"] - } - } - } - } - }, - { - "static": false, - "w": 2, - "moved": false, - "x": 2, - "h": 4, - "i": "09cc7897-4e96-44b3-9205-144fcf3ef5fb", - "y": 4, - "z": 0, - "config": { - "variables": ["{{variables.[Algolia Index]}}"], - "dataStream": { - "name": "noResultRate", - "pluginConfigId": "{{configId}}", - "id": "{{dataStreams.[noResultRate]}}", - "sort": { "by": [["date_byDay", "asc"]] }, - "group": { - "by": [["date", "byDay"]], - "aggregate": [{ "type": "mean", "names": ["rate"] }] - } - }, - "scope": { - "variable": "{{variables.[Algolia Index]}}", - "workspace": "{{workspaceId}}", - "scope": "{{scopes.[Algolia Indices]}}" - }, - "_type": "tile/data-stream", - "description": "", - "activePluginConfigIds": ["{{configId}}"], - "title": "No-Result Rate", - "visualisation": { - "type": "data-stream-line-graph", - "config": { - "data-stream-line-graph": { - "yAxisLabel": "No-Result Rate", - "xAxisColumn": "date_byDay", - "showLegend": false, - "showYAxisLabel": true, - "seriesColumn": "none", - "showTrendLine": false, - "legendPosition": "bottom", - "yAxisColumn": ["rate_mean"] - } - } - } - } - }, - { - "static": false, - "w": 2, - "moved": false, - "x": 0, - "h": 4, - "i": "1c816936-aafc-42dc-8096-10f0281a6e17", - "y": 0, - "z": 0, - "config": { - "variables": ["{{variables.[Algolia Index]}}"], - "dataStream": { - "name": "topSearches", - "pluginConfigId": "{{configId}}", - "id": "{{dataStreams.[topSearches]}}", - "sort": { "by": [["count", "desc"]] } - }, - "scope": { - "variable": "{{variables.[Algolia Index]}}", - "workspace": "{{workspaceId}}", - "scope": "{{scopes.[Algolia Indices]}}" - }, - "_type": "tile/data-stream", - "description": "", - "activePluginConfigIds": ["{{configId}}"], - "title": "Top Searches", - "visualisation": { - "type": "data-stream-table", - "config": { - "data-stream-table": { - "columnOrder": ["search", "count", "nbHits"], - "transpose": false - } - } - } - } - }, - { - "static": false, - "w": 2, - "moved": false, - "x": 2, - "h": 4, - "i": "1727a100-9c88-41cb-b157-c0f197b1b13e", - "y": 0, - "z": 0, - "config": { - "variables": ["{{variables.[Algolia Index]}}"], - "dataStream": { - "name": "noResultSearches", - "pluginConfigId": "{{configId}}", - "id": "{{dataStreams.[noResultSearches]}}", - "sort": { "by": [["count", "desc"]] } - }, - "scope": { - "variable": "{{variables.[Algolia Index]}}", - "workspace": "{{workspaceId}}", - "scope": "{{scopes.[Algolia Indices]}}" - }, - "_type": "tile/data-stream", - "description": "", - "activePluginConfigIds": ["{{configId}}"], - "title": "Searches With No Results", - "visualisation": { - "type": "data-stream-table", - "config": { - "data-stream-table": { - "columnOrder": ["search", "count", "withFilterCount"], - "transpose": false - } - } - } - } - } - ], - "columns": 4 - } + ], + "columns": 4 + } } diff --git a/.claude/skills/migrate-dashboard/references/source-example.json b/.claude/skills/migrate-dashboard/references/source-example.json index 7dd8c0a5..4e7758e0 100644 --- a/.claude/skills/migrate-dashboard/references/source-example.json +++ b/.claude/skills/migrate-dashboard/references/source-example.json @@ -1,210 +1,214 @@ { - "_type": "layout/grid", - "version": 1, - "contents": [ - { - "static": false, - "w": 4, - "moved": false, - "x": 0, - "h": 4, - "i": "9b9fc2b9-c56d-47de-a4c4-d151e467a210", - "y": 6, - "z": 0, - "config": { - "timeframe": "none", - "variables": ["var-J4pfoHDhrn2MRUQuPqv4"], - "dataStream": { - "pluginConfigId": "config-6PK2neo9yFh0rx3z80V2", - "id": "datastream-properties" - }, - "scope": { - "variable": "var-J4pfoHDhrn2MRUQuPqv4", - "workspace": "space-m86qNQgA8Mm39fbrpiNX", - "scope": "scope-mFB0VLVFCzanCpmI07nA" - }, - "_type": "tile/data-stream", - "description": "", - "activePluginConfigIds": ["config-6PK2neo9yFh0rx3z80V2"], - "title": "Index Details", - "visualisation": { - "type": "data-stream-table", - "config": { - "data-stream-table": { - "transpose": true + "_type": "layout/grid", + "version": 5, + "contents": [ + { + "static": false, + "w": 4, + "moved": false, + "x": 0, + "h": 4, + "i": "9b9fc2b9-c56d-47de-a4c4-d151e467a210", + "y": 6, + "z": 0, + "config": { + "timeframe": "none", + "variables": ["var-J4pfoHDhrn2MRUQuPqv4"], + "dataStream": { + "pluginConfigId": "config-6PK2neo9yFh0rx3z80V2", + "id": "datastream-properties" + }, + "scope": { + "variable": "var-J4pfoHDhrn2MRUQuPqv4", + "workspace": "space-m86qNQgA8Mm39fbrpiNX", + "scope": "scope-mFB0VLVFCzanCpmI07nA" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["config-6PK2neo9yFh0rx3z80V2"], + "title": "Index Details", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "transpose": true + } + } + } } - } - } - } - }, - { - "static": false, - "w": 2, - "moved": false, - "x": 0, - "h": 4, - "i": "dfa41039-1657-4142-b6a5-16ac7abffb9d", - "y": 4, - "z": 0, - "config": { - "variables": ["var-J4pfoHDhrn2MRUQuPqv4"], - "dataStream": { - "name": "searchCount", - "pluginConfigId": "config-6PK2neo9yFh0rx3z80V2", - "id": "datastream-GECbenNA3dhoyzEmkVrW", - "sort": { "by": [["date_byDay", "asc"]] }, - "group": { - "by": [["date", "byDay"]], - "aggregate": [{ "type": "sum", "names": ["count"] }] - } }, - "scope": { - "variable": "var-J4pfoHDhrn2MRUQuPqv4", - "workspace": "space-m86qNQgA8Mm39fbrpiNX", - "scope": "scope-mFB0VLVFCzanCpmI07nA" - }, - "_type": "tile/data-stream", - "description": "", - "activePluginConfigIds": ["config-6PK2neo9yFh0rx3z80V2"], - "title": "Search Volume", - "visualisation": { - "type": "data-stream-line-graph", - "config": { - "data-stream-line-graph": { - "yAxisLabel": "Searches", - "xAxisColumn": "date_byDay", - "showLegend": false, - "showYAxisLabel": true, - "seriesColumn": "none", - "showTrendLine": false, - "legendPosition": "bottom", - "yAxisColumn": ["count_sum"] + { + "static": false, + "w": 2, + "moved": false, + "x": 0, + "h": 4, + "i": "dfa41039-1657-4142-b6a5-16ac7abffb9d", + "y": 4, + "z": 0, + "config": { + "variables": ["var-J4pfoHDhrn2MRUQuPqv4"], + "dataStream": { + "name": "searchCount", + "pluginConfigId": "config-6PK2neo9yFh0rx3z80V2", + "id": "datastream-GECbenNA3dhoyzEmkVrW", + "sort": { "by": [["date_byDay", "asc"]] }, + "group": { + "by": [["date", "byDay"]], + "aggregate": [{ "type": "sum", "names": ["count"] }] + } + }, + "scope": { + "variable": "var-J4pfoHDhrn2MRUQuPqv4", + "workspace": "space-m86qNQgA8Mm39fbrpiNX", + "scope": "scope-mFB0VLVFCzanCpmI07nA" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["config-6PK2neo9yFh0rx3z80V2"], + "title": "Search Volume", + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "yAxisLabel": "Searches", + "xAxisColumn": "date_byDay", + "showLegend": false, + "showYAxisLabel": true, + "seriesColumn": "none", + "showTrendLine": false, + "legendPosition": "bottom", + "yAxisColumn": ["count_sum"] + } + } + } } - } - } - } - }, - { - "static": false, - "w": 2, - "moved": false, - "x": 2, - "h": 4, - "i": "09cc7897-4e96-44b3-9205-144fcf3ef5fb", - "y": 4, - "z": 0, - "config": { - "variables": ["var-J4pfoHDhrn2MRUQuPqv4"], - "dataStream": { - "name": "noResultRate", - "pluginConfigId": "config-6PK2neo9yFh0rx3z80V2", - "id": "datastream-WZlGFhDNuEpIX3axqpcR", - "sort": { "by": [["date_byDay", "asc"]] }, - "group": { - "by": [["date", "byDay"]], - "aggregate": [{ "type": "mean", "names": ["rate"] }] - } }, - "scope": { - "variable": "var-J4pfoHDhrn2MRUQuPqv4", - "workspace": "space-m86qNQgA8Mm39fbrpiNX", - "scope": "scope-mFB0VLVFCzanCpmI07nA" - }, - "_type": "tile/data-stream", - "description": "", - "activePluginConfigIds": ["config-6PK2neo9yFh0rx3z80V2"], - "title": "No-Result Rate", - "visualisation": { - "type": "data-stream-line-graph", - "config": { - "data-stream-line-graph": { - "yAxisLabel": "No-Result Rate", - "xAxisColumn": "date_byDay", - "showLegend": false, - "showYAxisLabel": true, - "seriesColumn": "none", - "showTrendLine": false, - "legendPosition": "bottom", - "yAxisColumn": ["rate_mean"] + { + "static": false, + "w": 2, + "moved": false, + "x": 2, + "h": 4, + "i": "09cc7897-4e96-44b3-9205-144fcf3ef5fb", + "y": 4, + "z": 0, + "config": { + "variables": ["var-J4pfoHDhrn2MRUQuPqv4"], + "dataStream": { + "name": "noResultRate", + "pluginConfigId": "config-6PK2neo9yFh0rx3z80V2", + "id": "datastream-WZlGFhDNuEpIX3axqpcR", + "sort": { "by": [["date_byDay", "asc"]] }, + "group": { + "by": [["date", "byDay"]], + "aggregate": [{ "type": "mean", "names": ["rate"] }] + } + }, + "scope": { + "variable": "var-J4pfoHDhrn2MRUQuPqv4", + "workspace": "space-m86qNQgA8Mm39fbrpiNX", + "scope": "scope-mFB0VLVFCzanCpmI07nA" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["config-6PK2neo9yFh0rx3z80V2"], + "title": "No-Result Rate", + "visualisation": { + "type": "data-stream-line-graph", + "config": { + "data-stream-line-graph": { + "yAxisLabel": "No-Result Rate", + "xAxisColumn": "date_byDay", + "showLegend": false, + "showYAxisLabel": true, + "seriesColumn": "none", + "showTrendLine": false, + "legendPosition": "bottom", + "yAxisColumn": ["rate_mean"] + } + } + } } - } - } - } - }, - { - "static": false, - "w": 2, - "moved": false, - "x": 0, - "h": 4, - "i": "1c816936-aafc-42dc-8096-10f0281a6e17", - "y": 0, - "z": 0, - "config": { - "variables": ["var-J4pfoHDhrn2MRUQuPqv4"], - "dataStream": { - "name": "topSearches", - "pluginConfigId": "config-6PK2neo9yFh0rx3z80V2", - "id": "datastream-f7fhFvXjv82JKG5Np0lc", - "sort": { "by": [["count", "desc"]] } - }, - "scope": { - "variable": "var-J4pfoHDhrn2MRUQuPqv4", - "workspace": "space-m86qNQgA8Mm39fbrpiNX", - "scope": "scope-mFB0VLVFCzanCpmI07nA" }, - "_type": "tile/data-stream", - "description": "", - "activePluginConfigIds": ["config-6PK2neo9yFh0rx3z80V2"], - "title": "Top Searches", - "visualisation": { - "type": "data-stream-table", - "config": { - "data-stream-table": { - "columnOrder": ["search", "count", "nbHits"], - "transpose": false + { + "static": false, + "w": 2, + "moved": false, + "x": 0, + "h": 4, + "i": "1c816936-aafc-42dc-8096-10f0281a6e17", + "y": 0, + "z": 0, + "config": { + "variables": ["var-J4pfoHDhrn2MRUQuPqv4"], + "dataStream": { + "name": "topSearches", + "pluginConfigId": "config-6PK2neo9yFh0rx3z80V2", + "id": "datastream-f7fhFvXjv82JKG5Np0lc", + "sort": { "by": [["count", "desc"]] } + }, + "scope": { + "variable": "var-J4pfoHDhrn2MRUQuPqv4", + "workspace": "space-m86qNQgA8Mm39fbrpiNX", + "scope": "scope-mFB0VLVFCzanCpmI07nA" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["config-6PK2neo9yFh0rx3z80V2"], + "title": "Top Searches", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": ["search", "count", "nbHits"], + "transpose": false + } + } + } } - } - } - } - }, - { - "static": false, - "w": 2, - "moved": false, - "x": 2, - "h": 4, - "i": "1727a100-9c88-41cb-b157-c0f197b1b13e", - "y": 0, - "z": 0, - "config": { - "variables": ["var-J4pfoHDhrn2MRUQuPqv4"], - "dataStream": { - "name": "noResultSearches", - "pluginConfigId": "config-6PK2neo9yFh0rx3z80V2", - "id": "datastream-Y85PbGnycRrMZ6eH6bSw", - "sort": { "by": [["count", "desc"]] } - }, - "scope": { - "variable": "var-J4pfoHDhrn2MRUQuPqv4", - "workspace": "space-m86qNQgA8Mm39fbrpiNX", - "scope": "scope-mFB0VLVFCzanCpmI07nA" }, - "_type": "tile/data-stream", - "description": "", - "activePluginConfigIds": ["config-6PK2neo9yFh0rx3z80V2"], - "title": "Searches With No Results", - "visualisation": { - "type": "data-stream-table", - "config": { - "data-stream-table": { - "columnOrder": ["search", "count", "withFilterCount"], - "transpose": false + { + "static": false, + "w": 2, + "moved": false, + "x": 2, + "h": 4, + "i": "1727a100-9c88-41cb-b157-c0f197b1b13e", + "y": 0, + "z": 0, + "config": { + "variables": ["var-J4pfoHDhrn2MRUQuPqv4"], + "dataStream": { + "name": "noResultSearches", + "pluginConfigId": "config-6PK2neo9yFh0rx3z80V2", + "id": "datastream-Y85PbGnycRrMZ6eH6bSw", + "sort": { "by": [["count", "desc"]] } + }, + "scope": { + "variable": "var-J4pfoHDhrn2MRUQuPqv4", + "workspace": "space-m86qNQgA8Mm39fbrpiNX", + "scope": "scope-mFB0VLVFCzanCpmI07nA" + }, + "_type": "tile/data-stream", + "description": "", + "activePluginConfigIds": ["config-6PK2neo9yFh0rx3z80V2"], + "title": "Searches With No Results", + "visualisation": { + "type": "data-stream-table", + "config": { + "data-stream-table": { + "columnOrder": [ + "search", + "count", + "withFilterCount" + ], + "transpose": false + } + } + } } - } } - } - } - ], - "columns": 4 + ], + "columns": 4 } diff --git a/.claude/skills/migrate-dashboard/scripts/normalize.js b/.claude/skills/migrate-dashboard/scripts/normalize.js index f1b6ef63..a27a440d 100644 --- a/.claude/skills/migrate-dashboard/scripts/normalize.js +++ b/.claude/skills/migrate-dashboard/scripts/normalize.js @@ -67,7 +67,10 @@ function normalizeTile(tile) { // Templatize dataStream if (config.dataStream) { - if (config.dataStream.name) { + // Leave global built-in data streams (e.g. datastream-properties) untouched — + // their id is not plugin-specific, so it stays as-is. + const isGlobalDataStream = config.dataStream.id === 'datastream-properties'; + if (config.dataStream.name && !isGlobalDataStream) { config.dataStream.id = `{{dataStreams.[${config.dataStream.name}]}}`; } if (config.dataStream.pluginConfigId) { @@ -110,6 +113,9 @@ function globalReplace(obj) { const normalized = globalReplace({ ...dashboard, + // Always start plugin default content at version 1, regardless of the + // version the dashboard had in the platform. + version: 1, contents: (dashboard.contents || []).map(normalizeTile), }); From 05ddeb89869df52f4d04c603678c44036f220b57 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Mon, 22 Jun 2026 10:58:11 +0100 Subject: [PATCH 4/5] ensure that skill does not change x/y/z positions of tiles --- .claude/skills/migrate-dashboard/SKILL.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.claude/skills/migrate-dashboard/SKILL.md b/.claude/skills/migrate-dashboard/SKILL.md index bfce6dce..4f2eb58b 100644 --- a/.claude/skills/migrate-dashboard/SKILL.md +++ b/.claude/skills/migrate-dashboard/SKILL.md @@ -58,6 +58,7 @@ Verify the output — check that: - `dashboard.version` is reset to `1` (the script forces this regardless of the pasted-in version) - `config.scopes[]` entries with `ids_defaultScopeIds` bindings are removed - Perspective tiles: `config.variables`, `config.scope.variable`, `config.scope.scope` are templatized +- The tiles x/y/z positions have not been changed from the pasted dashboard JSON **Done when:** no hardcoded platform IDs remain in any tile. From 987496446e931b85cb9f414bcdda437fa46e1565 Mon Sep 17 00:00:00 2001 From: Andrew Harris Date: Mon, 22 Jun 2026 11:00:07 +0100 Subject: [PATCH 5/5] Rename migrate-dashboard skill to convert-dashboard --- .../{migrate-dashboard => convert-dashboard}/SKILL.md | 6 +++--- .../references/migrated-example.json | 0 .../references/source-example.json | 0 .../scripts/normalize.js | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename .claude/skills/{migrate-dashboard => convert-dashboard}/SKILL.md (96%) rename .claude/skills/{migrate-dashboard => convert-dashboard}/references/migrated-example.json (100%) rename .claude/skills/{migrate-dashboard => convert-dashboard}/references/source-example.json (100%) rename .claude/skills/{migrate-dashboard => convert-dashboard}/scripts/normalize.js (100%) diff --git a/.claude/skills/migrate-dashboard/SKILL.md b/.claude/skills/convert-dashboard/SKILL.md similarity index 96% rename from .claude/skills/migrate-dashboard/SKILL.md rename to .claude/skills/convert-dashboard/SKILL.md index 4f2eb58b..1ed983b2 100644 --- a/.claude/skills/migrate-dashboard/SKILL.md +++ b/.claude/skills/convert-dashboard/SKILL.md @@ -1,5 +1,5 @@ --- -name: migrate-dashboard +name: convert-dashboard description: Migrate an exported platform dashboard JSON to plugin format. Use when the user pastes a dashboard JSON to convert, says "migrate this dashboard", or wants to add an exported dashboard to a plugin as default content. model: sonnet metadata: @@ -7,11 +7,11 @@ metadata: version: "0.0.1" --- -# Migrate Dashboard to Plugin Format +# Convert Dashboard to Plugin Format Convert a platform-exported dashboard JSON to plugin format by normalizing hardcoded IDs to template variables, then saving as plugin default content. -**Announce at start:** "I'm using the migrate-dashboard skill." +**Announce at start:** "I'm using the convert-dashboard skill." --- diff --git a/.claude/skills/migrate-dashboard/references/migrated-example.json b/.claude/skills/convert-dashboard/references/migrated-example.json similarity index 100% rename from .claude/skills/migrate-dashboard/references/migrated-example.json rename to .claude/skills/convert-dashboard/references/migrated-example.json diff --git a/.claude/skills/migrate-dashboard/references/source-example.json b/.claude/skills/convert-dashboard/references/source-example.json similarity index 100% rename from .claude/skills/migrate-dashboard/references/source-example.json rename to .claude/skills/convert-dashboard/references/source-example.json diff --git a/.claude/skills/migrate-dashboard/scripts/normalize.js b/.claude/skills/convert-dashboard/scripts/normalize.js similarity index 100% rename from .claude/skills/migrate-dashboard/scripts/normalize.js rename to .claude/skills/convert-dashboard/scripts/normalize.js