@luquimbo/bi-superpowers 4.1.2 → 4.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.claude-plugin/skill-manifest.json +1 -1
- package/.plugin/plugin.json +1 -1
- package/AGENTS.md +9 -7
- package/CHANGELOG.md +40 -0
- package/README.md +637 -96
- package/bin/cli.js +76 -61
- package/bin/commands/build-desktop.js +60 -6
- package/bin/commands/diff.js +86 -1
- package/bin/commands/mcp-setup.js +26 -3
- package/bin/commands/watch.js +50 -5
- package/bin/postinstall.js +1 -1
- package/bin/utils/mcp-detect.js +1 -1
- package/commands/bi-start.md +3 -3
- package/commands/pbi-connect.md +60 -24
- package/commands/report-design.md +1 -1
- package/desktop-extension/server.js +43 -10
- package/package.json +3 -4
- package/skills/bi-start/SKILL.md +4 -4
- package/skills/bi-start/scripts/update-check.js +1 -1
- package/skills/pbi-connect/SKILL.md +61 -25
- package/skills/pbi-connect/scripts/update-check.js +1 -1
- package/skills/project-kickoff/SKILL.md +1 -1
- package/skills/project-kickoff/scripts/update-check.js +1 -1
- package/skills/report-design/SKILL.md +2 -2
- package/skills/report-design/references/layouts/finance.md +2 -2
- package/skills/report-design/references/native-visuals.md +2 -2
- package/skills/report-design/references/slicer.md +1 -1
- package/skills/report-design/references/textbox.md +1 -1
- package/skills/report-design/scripts/create-visual.js +65 -1
- package/skills/report-design/scripts/update-check.js +1 -1
- package/skills/report-design/scripts/validate-pbir.js +29 -0
- package/src/content/base.md +1 -1
- package/src/content/routing.md +1 -1
- package/src/content/skills/bi-start.md +3 -3
- package/src/content/skills/pbi-connect.md +60 -24
- package/src/content/skills/report-design/SKILL.md +1 -1
- package/src/content/skills/report-design/references/layouts/finance.md +2 -2
- package/src/content/skills/report-design/references/native-visuals.md +2 -2
- package/src/content/skills/report-design/references/slicer.md +1 -1
- package/src/content/skills/report-design/references/textbox.md +1 -1
- package/src/content/skills/report-design/scripts/create-visual.js +65 -1
- package/src/content/skills/report-design/scripts/validate-pbir.js +29 -0
|
@@ -679,6 +679,66 @@ function nextInt(existing, key) {
|
|
|
679
679
|
return max + 1;
|
|
680
680
|
}
|
|
681
681
|
|
|
682
|
+
function validateVisualName(name) {
|
|
683
|
+
if (typeof name !== 'string' || name.trim() === '') {
|
|
684
|
+
fail('--name must be a non-empty visual folder name');
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (name !== name.trim()) {
|
|
688
|
+
fail(`--name must not have leading or trailing whitespace (got: ${JSON.stringify(name)})`);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
if (
|
|
692
|
+
name === '.' ||
|
|
693
|
+
name === '..' ||
|
|
694
|
+
/[\\/]/.test(name) ||
|
|
695
|
+
path.basename(name) !== name ||
|
|
696
|
+
path.win32.basename(name) !== name ||
|
|
697
|
+
path.posix.basename(name) !== name
|
|
698
|
+
) {
|
|
699
|
+
fail(`--name must be a single visual folder name, not a path (got: ${JSON.stringify(name)})`);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (/[\u0000-\u001f<>:"|?*]/.test(name)) {
|
|
703
|
+
fail(`--name contains characters that are not valid in a Windows folder name (got: ${JSON.stringify(name)})`);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function validateNumericOptions(args) {
|
|
708
|
+
const numericOptions = [
|
|
709
|
+
['x', '--x'],
|
|
710
|
+
['y', '--y'],
|
|
711
|
+
['z', '-z'],
|
|
712
|
+
['width', '--width'],
|
|
713
|
+
['height', '--height'],
|
|
714
|
+
['tabOrder', '--tab-order'],
|
|
715
|
+
];
|
|
716
|
+
|
|
717
|
+
for (const [key, flag] of numericOptions) {
|
|
718
|
+
if (args[key] != null && !Number.isFinite(args[key])) {
|
|
719
|
+
fail(`${flag} must be a finite number`);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
for (const [key, flag] of [
|
|
724
|
+
['width', '--width'],
|
|
725
|
+
['height', '--height'],
|
|
726
|
+
]) {
|
|
727
|
+
if (args[key] != null && args[key] <= 0) {
|
|
728
|
+
fail(`${flag} must be greater than 0`);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function assertPathInside(childPath, parentPath, label) {
|
|
734
|
+
const parent = path.resolve(parentPath);
|
|
735
|
+
const child = path.resolve(childPath);
|
|
736
|
+
const relative = path.relative(parent, child);
|
|
737
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
738
|
+
fail(`${label} resolved outside the expected directory: ${child}`);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
682
742
|
// ---------------------------------------------------------------------------
|
|
683
743
|
// main()
|
|
684
744
|
// ---------------------------------------------------------------------------
|
|
@@ -698,6 +758,7 @@ function main() {
|
|
|
698
758
|
if (!args.reportPath) fail('--report-path is required');
|
|
699
759
|
if (!args.page) fail('--page is required');
|
|
700
760
|
if (!args.type) fail('--type is required (use --list-types to see options)');
|
|
761
|
+
validateNumericOptions(args);
|
|
701
762
|
|
|
702
763
|
// ---- type validation with friendly errors for known non-native aliases
|
|
703
764
|
if (KNOWN_NON_NATIVE_TYPES[args.type]) {
|
|
@@ -727,8 +788,11 @@ function main() {
|
|
|
727
788
|
} while (existing.some((v) => v.name === candidate));
|
|
728
789
|
name = candidate;
|
|
729
790
|
}
|
|
791
|
+
validateVisualName(name);
|
|
730
792
|
|
|
731
|
-
const
|
|
793
|
+
const visualsDir = path.join(pageDir, 'visuals');
|
|
794
|
+
const visualDir = path.join(visualsDir, name);
|
|
795
|
+
assertPathInside(visualDir, visualsDir, '--name');
|
|
732
796
|
const visualJsonPath = path.join(visualDir, 'visual.json');
|
|
733
797
|
if (fs.existsSync(visualJsonPath)) {
|
|
734
798
|
fail(
|
|
@@ -47,7 +47,7 @@ const HTTPS_TIMEOUT_MS = 5000;
|
|
|
47
47
|
// Rewritten at generation time when this helper is copied into
|
|
48
48
|
// `skills/<name>/scripts/update-check.js`. In the canonical source under
|
|
49
49
|
// `bin/commands/`, it stays null and we fall back to package.json.
|
|
50
|
-
const BUNDLED_INSTALLED_VERSION = "4.1.
|
|
50
|
+
const BUNDLED_INSTALLED_VERSION = "4.1.4";
|
|
51
51
|
|
|
52
52
|
// ---------------------------------------------------------------------------
|
|
53
53
|
// Argument parsing
|
|
@@ -130,6 +130,33 @@ function boundRoles(visualData) {
|
|
|
130
130
|
return bound;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
function validatePosition(position) {
|
|
134
|
+
const errors = [];
|
|
135
|
+
const requiredFields = ['x', 'y', 'z', 'height', 'width', 'tabOrder'];
|
|
136
|
+
|
|
137
|
+
if (!position || typeof position !== 'object') {
|
|
138
|
+
return requiredFields.map((field) => ({
|
|
139
|
+
severity: 'error',
|
|
140
|
+
rule: 'position-number',
|
|
141
|
+
field,
|
|
142
|
+
message: `position.${field} must be a finite number`,
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (const field of requiredFields) {
|
|
147
|
+
if (!Number.isFinite(position[field])) {
|
|
148
|
+
errors.push({
|
|
149
|
+
severity: 'error',
|
|
150
|
+
rule: 'position-number',
|
|
151
|
+
field,
|
|
152
|
+
message: `position.${field} must be a finite number`,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return errors;
|
|
158
|
+
}
|
|
159
|
+
|
|
133
160
|
function validateVisual(visual) {
|
|
134
161
|
const errors = [];
|
|
135
162
|
const { data, path: filePath } = visual;
|
|
@@ -148,6 +175,8 @@ function validateVisual(visual) {
|
|
|
148
175
|
errors.push({ severity: 'error', rule: 'name', message: 'missing "name"' });
|
|
149
176
|
}
|
|
150
177
|
|
|
178
|
+
errors.push(...validatePosition(data.position));
|
|
179
|
+
|
|
151
180
|
const vt = data.visual && data.visual.visualType;
|
|
152
181
|
if (!vt) {
|
|
153
182
|
errors.push({ severity: 'error', rule: 'visual-type', message: 'missing visualType' });
|
package/src/content/base.md
CHANGED
|
@@ -142,7 +142,7 @@ AI Assistant → microsoft-learn HTTP MCP → learn.microsoft.com docs
|
|
|
142
142
|
|
|
143
143
|
### Practical guidance
|
|
144
144
|
|
|
145
|
-
- Prefer `.mcp.json`
|
|
145
|
+
- Prefer user-level agent MCP config files written by `super install`. Use project-root `.mcp.json` only when the user explicitly uses an optional local Claude Code plugin.
|
|
146
146
|
- Never invent or hardcode local ports for the official Modeling MCP flow.
|
|
147
147
|
- On macOS/Linux, explain that the local Modeling MCP is unavailable and fall back to `microsoft-learn` for docs. Live editing of a local model requires Windows.
|
|
148
148
|
|
package/src/content/routing.md
CHANGED
|
@@ -52,7 +52,7 @@ Scan current directory for relevant files:
|
|
|
52
52
|
|----------------|-----------------|
|
|
53
53
|
| `.pbix` / `.pbip` / `.tmdl` files | `/project-kickoff` (if not analyzed) |
|
|
54
54
|
| `.xlsx` / `.xlsm` files | `/project-kickoff` |
|
|
55
|
-
|
|
|
55
|
+
| Agent MCP config missing when user asks about Power BI | `/pbi-connect` |
|
|
56
56
|
|
|
57
57
|
### 4. CHECK KEYWORDS
|
|
58
58
|
|
|
@@ -49,7 +49,7 @@ Interpret the single-line output:
|
|
|
49
49
|
```bash
|
|
50
50
|
super upgrade
|
|
51
51
|
```
|
|
52
|
-
|
|
52
|
+
After it finishes, remind: _"Corré `super install --all --yes` para refrescar la instalación user-level de todos los agentes. Solo si además mantenés un plugin local de Claude Code generado con `super kickoff`, corré `super recharge` dentro de ese repo."_
|
|
53
53
|
|
|
54
54
|
On `no` — respect it, continue to PHASE 1 silently. The update-state.json already tracks the user's snooze per `update-check.js` semantics.
|
|
55
55
|
|
|
@@ -80,7 +80,7 @@ Do these detections in order:
|
|
|
80
80
|
```
|
|
81
81
|
(or equivalent). `$pbiDesktopRunning = true` if present.
|
|
82
82
|
|
|
83
|
-
4. **MCP configured**: look for
|
|
83
|
+
4. **MCP configured**: look first for `powerbi-modeling-mcp` in the agent's user-level config file (`~/.claude.json`, `~/.codex/config.toml`, etc). Only check project-root `.mcp.json` when the user explicitly says they use a local Claude Code plugin. Keep the check shallow — no need to deep-diff.
|
|
84
84
|
|
|
85
85
|
### Emit the context in 3-4 lines max
|
|
86
86
|
|
|
@@ -174,7 +174,7 @@ Stop. Don't hover. The user will tell you what they want next.
|
|
|
174
174
|
- **Project analysis or setup**: that's `/project-kickoff`. If the user says "analizar mi proyecto", "armar el modelo base", "arrancar uno nuevo desde cero", delegate.
|
|
175
175
|
- **MCP wiring details**: that's `/pbi-connect`. bi-start just offers to dispatch it; the actual configuration work is in that skill.
|
|
176
176
|
- **Report authoring**: that's `/report-design`. Same pattern.
|
|
177
|
-
- **Running the update**: bi-start offers + dispatches `super upgrade`; the actual refresh path after eso (`/plugin update bi-superpowers`, `super install --yes`, o `super recharge`) is owned by `/bin/cli.js`.
|
|
177
|
+
- **Running the update**: bi-start offers + dispatches `super upgrade`; the actual refresh path after eso (`/plugin update bi-superpowers`, `super install --all --yes`, o `super recharge` only for local Claude Code plugins) is owned by `/bin/cli.js`.
|
|
178
178
|
|
|
179
179
|
## Related Skills
|
|
180
180
|
|
|
@@ -9,10 +9,10 @@ Activate this skill when user mentions:
|
|
|
9
9
|
- "can't connect to Power BI", "connection error", "MCP not working"
|
|
10
10
|
|
|
11
11
|
## Identity
|
|
12
|
-
You are a **Power BI MCP Connection Specialist**. Your job is to help the user connect their AI agent to Power BI Desktop using the official Microsoft MCP servers shipped with bi-superpowers, with a
|
|
12
|
+
You are a **Power BI MCP Connection Specialist**. Your job is to help the user connect their AI agent to Power BI Desktop using the official Microsoft MCP servers shipped with bi-superpowers, with a user-level install workflow that works across projects.
|
|
13
13
|
|
|
14
14
|
## MANDATORY RULES
|
|
15
|
-
1. **
|
|
15
|
+
1. **USER-LEVEL FIRST.** Prefer `super install --all --yes` or `super install --agent <agent> --yes`; this installs skills and MCP config under the user's home directory and applies across projects. `.mcp.json` is only for an optional repo-local Claude Code plugin.
|
|
16
16
|
2. **OFFICIAL SERVERS ONLY.** Use `powerbi-modeling-mcp` (local) and `microsoft-learn` (HTTP). Do not invent or recommend unofficial MCPs.
|
|
17
17
|
3. **WINDOWS LIMITATION.** Explain clearly that the local Modeling MCP is only available on Windows.
|
|
18
18
|
4. **NO PORT INVENTION.** Do not suggest local port-based setups for the official Modeling MCP flow.
|
|
@@ -33,7 +33,8 @@ I'll help you connect your AI agent using the official Microsoft MCP servers.
|
|
|
33
33
|
What do you need?
|
|
34
34
|
|
|
35
35
|
1. Connect to Power BI Desktop on this machine (Windows)
|
|
36
|
-
2. Verify that my
|
|
36
|
+
2. Verify that my agent MCP config is installed correctly
|
|
37
|
+
3. Verify an optional local Claude Code plugin `.mcp.json`
|
|
37
38
|
```
|
|
38
39
|
|
|
39
40
|
---
|
|
@@ -56,11 +57,21 @@ Before we continue:
|
|
|
56
57
|
|
|
57
58
|
### If the user is on Windows and installed the extension
|
|
58
59
|
|
|
59
|
-
Guide them to:
|
|
60
|
+
Guide them to the user-level install:
|
|
60
61
|
|
|
61
|
-
1. Run `
|
|
62
|
-
2. Confirm
|
|
63
|
-
3. Restart or refresh
|
|
62
|
+
1. Run `super install --all --yes`, or for one agent run `super install --agent codex --yes` / `super install --agent claude-code --yes` / etc.
|
|
63
|
+
2. Confirm the agent config contains `powerbi-modeling-mcp` and `microsoft-learn`.
|
|
64
|
+
3. Restart or refresh the AI agent so it reloads skills and MCP servers.
|
|
65
|
+
|
|
66
|
+
Use these config locations:
|
|
67
|
+
|
|
68
|
+
| Agent | Skill path | MCP config |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| Claude Code | `~/.claude/skills` or `~/.agents/skills` | `~/.claude.json` |
|
|
71
|
+
| GitHub Copilot | `~/.copilot/skills` | `~/.copilot/mcp-config.json` |
|
|
72
|
+
| Codex | `~/.agents/skills` | `~/.codex/config.toml` |
|
|
73
|
+
| Gemini CLI | `~/.gemini/skills` | `~/.gemini/settings.json` |
|
|
74
|
+
| Kilo Code | `~/.kilo/skills` | `~/.kilo/mcp_settings.json` |
|
|
64
75
|
|
|
65
76
|
Use this explanation:
|
|
66
77
|
|
|
@@ -72,18 +83,13 @@ and starts it with `--start`.
|
|
|
72
83
|
|
|
73
84
|
If the user wants a config example, show:
|
|
74
85
|
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
"microsoft-learn": {
|
|
83
|
-
"type": "http",
|
|
84
|
-
"url": "https://learn.microsoft.com/api/mcp"
|
|
85
|
-
}
|
|
86
|
-
}
|
|
86
|
+
```toml
|
|
87
|
+
[mcp_servers.powerbi-modeling-mcp]
|
|
88
|
+
command = "node"
|
|
89
|
+
args = ["<package-dir>/bin/mcp/powerbi-modeling-launcher.js"]
|
|
90
|
+
|
|
91
|
+
[mcp_servers.microsoft-learn]
|
|
92
|
+
url = "https://learn.microsoft.com/api/mcp"
|
|
87
93
|
```
|
|
88
94
|
|
|
89
95
|
### If the user installed the executable manually
|
|
@@ -97,9 +103,11 @@ BI_SUPERPOWERS_POWERBI_MODELING_MCP_PATH
|
|
|
97
103
|
Then re-run:
|
|
98
104
|
|
|
99
105
|
```bash
|
|
100
|
-
super
|
|
106
|
+
super install --all --yes
|
|
101
107
|
```
|
|
102
108
|
|
|
109
|
+
If they are intentionally maintaining a repo-local Claude Code plugin, they can run `super mcp-setup` inside that plugin project instead.
|
|
110
|
+
|
|
103
111
|
### If the user is on macOS or Linux
|
|
104
112
|
|
|
105
113
|
Say:
|
|
@@ -114,15 +122,41 @@ For live editing of a local semantic model, you need a Windows environment.
|
|
|
114
122
|
|
|
115
123
|
---
|
|
116
124
|
|
|
117
|
-
## PHASE 2: Verify
|
|
125
|
+
## PHASE 2: Verify Agent MCP Config
|
|
118
126
|
|
|
119
127
|
If the user chooses option 2:
|
|
120
128
|
|
|
129
|
+
Check the config for the agent they use:
|
|
130
|
+
|
|
131
|
+
- Claude Code: `~/.claude.json`
|
|
132
|
+
- GitHub Copilot: `~/.copilot/mcp-config.json`
|
|
133
|
+
- Codex: `~/.codex/config.toml`
|
|
134
|
+
- Gemini CLI: `~/.gemini/settings.json`
|
|
135
|
+
- Kilo Code: `~/.kilo/mcp_settings.json`
|
|
136
|
+
|
|
137
|
+
Confirm:
|
|
138
|
+
|
|
139
|
+
- skills are installed under the agent's user-level skill directory
|
|
140
|
+
- config includes `powerbi-modeling-mcp`
|
|
141
|
+
- config includes `microsoft-learn`
|
|
142
|
+
|
|
143
|
+
If anything is missing, recommend:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
super install --agent <agent-id> --yes
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Then restart or refresh the agent.
|
|
150
|
+
|
|
151
|
+
## PHASE 3: Verify Optional Local Claude Code Plugin Config
|
|
152
|
+
|
|
153
|
+
If the user chooses option 3, or explicitly says they use `super kickoff` / `claude --plugin-dir`:
|
|
154
|
+
|
|
121
155
|
Check these files in order:
|
|
122
156
|
|
|
123
157
|
1. `.claude-plugin/plugin.json`
|
|
124
158
|
2. `.mcp.json`
|
|
125
|
-
3. `.bi-superpowers.json`
|
|
159
|
+
3. `.bi-superpowers.json`
|
|
126
160
|
|
|
127
161
|
Confirm:
|
|
128
162
|
|
|
@@ -151,7 +185,8 @@ claude --plugin-dir .
|
|
|
151
185
|
| --- | --- |
|
|
152
186
|
| Modeling MCP missing on Windows | Install the Microsoft extension in VS Code or Cursor |
|
|
153
187
|
| Modeling MCP installed manually | Set `BI_SUPERPOWERS_POWERBI_MODELING_MCP_PATH` |
|
|
154
|
-
|
|
|
188
|
+
| Agent not loading MCPs | Re-run `super install --agent <agent-id> --yes` and restart the agent |
|
|
189
|
+
| Local Claude Code plugin not loading MCPs | Re-run `super mcp-setup` inside the plugin project and restart Claude Code |
|
|
155
190
|
| macOS/Linux local modeling request | Use `microsoft-learn` for docs; live editing requires Windows |
|
|
156
191
|
| User asks about Excel MCP | Explain Excel remains supported through skills and library content, not a default MCP |
|
|
157
192
|
|
|
@@ -163,7 +198,8 @@ claude --plugin-dir .
|
|
|
163
198
|
| --- | --- | --- |
|
|
164
199
|
| Recommend `uvx` for Modeling MCP | Not the official Microsoft installation path | Use the official executable via the local launcher |
|
|
165
200
|
| Ask the user to find a localhost port | Not required in the new flow | Use the official Modeling MCP launcher |
|
|
166
|
-
|
|
|
201
|
+
| Run `super kickoff` for Codex/GitHub Copilot/Gemini/Kilo setup | `kickoff` creates repo-local Claude Code plugin files | Use `super install --agent <agent-id> --yes` |
|
|
202
|
+
| Treat `.mcp.json` as the default install target | It is only for optional local Claude Code plugins | Use the agent's user-level MCP config |
|
|
167
203
|
| Invent unofficial MCPs (remote, fabric, etc.) | This plugin only ships 2 official MCPs | Only use the 2 official MCPs we ship (`powerbi-modeling-mcp` and `microsoft-learn`) |
|
|
168
204
|
|
|
169
205
|
---
|
|
@@ -266,7 +266,7 @@ Do NOT hand-author `report.json` theme metadata. The canonical PBIR shape (for r
|
|
|
266
266
|
- `resourcePackages` includes a `{name: "RegisteredResources", type: "RegisteredResources"}` package whose `items[]` has `{name: "<file>.json", path: "<file>.json", type: "CustomTheme"}`
|
|
267
267
|
- the theme file itself lives at `<reportPath>/StaticResources/RegisteredResources/<file>.json`
|
|
268
268
|
|
|
269
|
-
**Conditional formatting
|
|
269
|
+
**Conditional formatting note.** Layout notes may describe variance colors, diverging matrix gradients, or signed-color bars as design intent. The current `report-design` flow does not author those formatting rules. Render those visuals with theme/default colors unless you have a tested PBIR formatting implementation. Do not invent `pbi format` calls from this skill.
|
|
270
270
|
|
|
271
271
|
### 4.4 Validate (two layers)
|
|
272
272
|
|
|
@@ -28,7 +28,7 @@ Roles used below (to be resolved via the MCP):
|
|
|
28
28
|
| Scenario slicer | slicer (manual JSON — see `references/slicer.md`) | 848, 312, 336, 80 | `Scenario[Scenario]` |
|
|
29
29
|
|
|
30
30
|
**Design notes:**
|
|
31
|
-
- Variance KPI:
|
|
31
|
+
- Variance KPI: green/red conditional coloring is design intent only. Render as a plain card with theme/default colors unless a tested PBIR formatting implementation is available.
|
|
32
32
|
- Year slicer default: current year (single-select).
|
|
33
33
|
|
|
34
34
|
---
|
|
@@ -61,5 +61,5 @@ Roles used below (to be resolved via the MCP):
|
|
|
61
61
|
| Account × Month matrix | matrix | 16, 512, 1168, 144 | rows: `account-dim-column`, cols: `'Date'[Month]`, values: `variance-measure` with diverging gradient |
|
|
62
62
|
|
|
63
63
|
**Design notes:**
|
|
64
|
-
- Variance-sign coloring (diverging palette) is
|
|
64
|
+
- Variance-sign coloring (diverging palette) is design intent only. Render bars/matrix with theme/default colors unless a tested PBIR formatting implementation is available.
|
|
65
65
|
- The bottom matrix is short (144px) so it acts as a "heatmap strip" rather than a scrollable table.
|
|
@@ -311,7 +311,7 @@ Si PBI Desktop introduce un visualType nuevo (o encontrás uno nativo que falta
|
|
|
311
311
|
|
|
312
312
|
## Features de `visual.json` que `create-visual.js` NO soporta todavía
|
|
313
313
|
|
|
314
|
-
Descubiertos al regenerar la smoke-test "Galería de Visuales" end-to-end con el script
|
|
314
|
+
Descubiertos al regenerar la smoke-test "Galería de Visuales" end-to-end con el script. No bloquean el authoring — la shape que emite el script pasa `pbi report validate` y `validate-pbir.js` — pero sí dejan regresiones visuales si el `visual.json` hand-written aprovechaba alguno de estos features:
|
|
315
315
|
|
|
316
316
|
1. **`query.sortDefinition`** — sort explícito por medida/columna con dirección. `create-visual.js` no lo emite, así que un visual "ranking" tipo barChart ordenado descendente por una medida pierde el orden en la regeneración. **Workaround**: ordenar en Desktop manualmente después del `.pbip` abrir, o hand-extender el `visual.json` con `sortDefinition` post-creación. **Fix futuro**: `--sort-by "Role:Field" --sort-dir descending` en `create-visual.js`.
|
|
317
317
|
|
|
@@ -319,7 +319,7 @@ Descubiertos al regenerar la smoke-test "Galería de Visuales" end-to-end con el
|
|
|
319
319
|
|
|
320
320
|
3. **`queryState.<Role>.projections[].active`** — las columnas siempre salen con `active: true`, las medidas nunca. Si un visual hand-written tenía `active: true` en una medida (para indicar que la medida es la "default Y") o `active: false` en una columna (hidden projection), el script normaliza. Rara vez significativo para el render pero cambia el shape JSON.
|
|
321
321
|
|
|
322
|
-
4. **Formato condicional**: colores signados (positivo verde / negativo rojo), gradientes en matriz, data bars en tabla. Ninguno de estos expresa shape via `create-visual.js
|
|
322
|
+
4. **Formato condicional**: colores signados (positivo verde / negativo rojo), gradientes en matriz, data bars en tabla. Ninguno de estos expresa shape via `create-visual.js`; tratarlos como diseño previsto hasta que exista una implementación PBIR testeada.
|
|
323
323
|
|
|
324
324
|
Cualquier cambio en el script que cubra estos gaps debe: (a) agregar el flag a `parseArgs`, (b) cubrirlo con tests en `create-visual.test.js`, (c) documentarlo en este archivo, (d) regenerar la Galería para que ejercite el feature.
|
|
325
325
|
|
|
@@ -86,4 +86,4 @@ cat > "<reportPath>/definition/pages/<pageName>/visuals/<visualName>/visual.json
|
|
|
86
86
|
EOF
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
Prefer `scripts/create-visual.js --type slicer` for new slicers. Keep the heredoc pattern above only as a fallback when the helper script is unavailable.
|
|
@@ -98,4 +98,4 @@ cat > "<reportPath>/definition/pages/<pageName>/visuals/<visualName>/visual.json
|
|
|
98
98
|
EOF
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
Prefer `scripts/create-visual.js --type textbox` for new textboxes. Keep the heredoc pattern above only as a fallback when the helper script is unavailable.
|
|
@@ -679,6 +679,66 @@ function nextInt(existing, key) {
|
|
|
679
679
|
return max + 1;
|
|
680
680
|
}
|
|
681
681
|
|
|
682
|
+
function validateVisualName(name) {
|
|
683
|
+
if (typeof name !== 'string' || name.trim() === '') {
|
|
684
|
+
fail('--name must be a non-empty visual folder name');
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (name !== name.trim()) {
|
|
688
|
+
fail(`--name must not have leading or trailing whitespace (got: ${JSON.stringify(name)})`);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
if (
|
|
692
|
+
name === '.' ||
|
|
693
|
+
name === '..' ||
|
|
694
|
+
/[\\/]/.test(name) ||
|
|
695
|
+
path.basename(name) !== name ||
|
|
696
|
+
path.win32.basename(name) !== name ||
|
|
697
|
+
path.posix.basename(name) !== name
|
|
698
|
+
) {
|
|
699
|
+
fail(`--name must be a single visual folder name, not a path (got: ${JSON.stringify(name)})`);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (/[\u0000-\u001f<>:"|?*]/.test(name)) {
|
|
703
|
+
fail(`--name contains characters that are not valid in a Windows folder name (got: ${JSON.stringify(name)})`);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function validateNumericOptions(args) {
|
|
708
|
+
const numericOptions = [
|
|
709
|
+
['x', '--x'],
|
|
710
|
+
['y', '--y'],
|
|
711
|
+
['z', '-z'],
|
|
712
|
+
['width', '--width'],
|
|
713
|
+
['height', '--height'],
|
|
714
|
+
['tabOrder', '--tab-order'],
|
|
715
|
+
];
|
|
716
|
+
|
|
717
|
+
for (const [key, flag] of numericOptions) {
|
|
718
|
+
if (args[key] != null && !Number.isFinite(args[key])) {
|
|
719
|
+
fail(`${flag} must be a finite number`);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
for (const [key, flag] of [
|
|
724
|
+
['width', '--width'],
|
|
725
|
+
['height', '--height'],
|
|
726
|
+
]) {
|
|
727
|
+
if (args[key] != null && args[key] <= 0) {
|
|
728
|
+
fail(`${flag} must be greater than 0`);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function assertPathInside(childPath, parentPath, label) {
|
|
734
|
+
const parent = path.resolve(parentPath);
|
|
735
|
+
const child = path.resolve(childPath);
|
|
736
|
+
const relative = path.relative(parent, child);
|
|
737
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
738
|
+
fail(`${label} resolved outside the expected directory: ${child}`);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
682
742
|
// ---------------------------------------------------------------------------
|
|
683
743
|
// main()
|
|
684
744
|
// ---------------------------------------------------------------------------
|
|
@@ -698,6 +758,7 @@ function main() {
|
|
|
698
758
|
if (!args.reportPath) fail('--report-path is required');
|
|
699
759
|
if (!args.page) fail('--page is required');
|
|
700
760
|
if (!args.type) fail('--type is required (use --list-types to see options)');
|
|
761
|
+
validateNumericOptions(args);
|
|
701
762
|
|
|
702
763
|
// ---- type validation with friendly errors for known non-native aliases
|
|
703
764
|
if (KNOWN_NON_NATIVE_TYPES[args.type]) {
|
|
@@ -727,8 +788,11 @@ function main() {
|
|
|
727
788
|
} while (existing.some((v) => v.name === candidate));
|
|
728
789
|
name = candidate;
|
|
729
790
|
}
|
|
791
|
+
validateVisualName(name);
|
|
730
792
|
|
|
731
|
-
const
|
|
793
|
+
const visualsDir = path.join(pageDir, 'visuals');
|
|
794
|
+
const visualDir = path.join(visualsDir, name);
|
|
795
|
+
assertPathInside(visualDir, visualsDir, '--name');
|
|
732
796
|
const visualJsonPath = path.join(visualDir, 'visual.json');
|
|
733
797
|
if (fs.existsSync(visualJsonPath)) {
|
|
734
798
|
fail(
|
|
@@ -130,6 +130,33 @@ function boundRoles(visualData) {
|
|
|
130
130
|
return bound;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
function validatePosition(position) {
|
|
134
|
+
const errors = [];
|
|
135
|
+
const requiredFields = ['x', 'y', 'z', 'height', 'width', 'tabOrder'];
|
|
136
|
+
|
|
137
|
+
if (!position || typeof position !== 'object') {
|
|
138
|
+
return requiredFields.map((field) => ({
|
|
139
|
+
severity: 'error',
|
|
140
|
+
rule: 'position-number',
|
|
141
|
+
field,
|
|
142
|
+
message: `position.${field} must be a finite number`,
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (const field of requiredFields) {
|
|
147
|
+
if (!Number.isFinite(position[field])) {
|
|
148
|
+
errors.push({
|
|
149
|
+
severity: 'error',
|
|
150
|
+
rule: 'position-number',
|
|
151
|
+
field,
|
|
152
|
+
message: `position.${field} must be a finite number`,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return errors;
|
|
158
|
+
}
|
|
159
|
+
|
|
133
160
|
function validateVisual(visual) {
|
|
134
161
|
const errors = [];
|
|
135
162
|
const { data, path: filePath } = visual;
|
|
@@ -148,6 +175,8 @@ function validateVisual(visual) {
|
|
|
148
175
|
errors.push({ severity: 'error', rule: 'name', message: 'missing "name"' });
|
|
149
176
|
}
|
|
150
177
|
|
|
178
|
+
errors.push(...validatePosition(data.position));
|
|
179
|
+
|
|
151
180
|
const vt = data.visual && data.visual.visualType;
|
|
152
181
|
if (!vt) {
|
|
153
182
|
errors.push({ severity: 'error', rule: 'visual-type', message: 'missing visualType' });
|