---
name: minasaad1/Power BI Custom Visuals
source: https://app.decimal.ai/s/minasaad1-power-bi-custom-visuals@1/SKILL.md
source_sha256: 9fd6730a1569
---

# Power BI Custom Visuals Skill

Build new Power BI custom visuals from natural language using the
`powerbi-visuals-tools` SDK. Iteration is agent-driven on TypeScript
compile errors; visual correctness is checked once at the end with the
user.

This skill produces a `.pbiviz` package and embeds it into a PBIR report
via `pbi visual import-custom`. The TypeScript project lives **next to**
the `.pbip` folder, never inside it.

## Prerequisites

The skill needs **Node.js** on PATH and the `powerbi-visuals-tools` npm
package. Both are installed on first run with the user's consent; never
silently.

Pinned versions (override via env vars if needed):

- `powerbi-visuals-tools@^5.6.0` (env: `PBIVIZ_VERSION`)
- `powerbi-visuals-api@^5.11.0` (pinned in scaffolded `package.json`)

The skill's edit patterns and AGENTS.md crib were written against these
versions. Bump deliberately.

### First-run prerequisite check

```bash
# 1. Probe Node
node --version || true
```

If `node` is missing, **ask the user**:

> Node.js isn't installed. Custom visual development needs it. Install
> now? (yes/no)
>
> - Windows: `winget install OpenJS.NodeJS.LTS`
> - macOS: `brew install node`
> - Linux: use your package manager or nvm

If user says yes, run the install command appropriate for their OS and
re-probe. If user says no, stop the skill with a clear message.

`pbiviz` itself runs through `npx --yes powerbi-visuals-tools@^5.6.0`,
so there's no global install of the CLI itself. The first `npx`
invocation will fetch and cache it locally.

## Discovery: existing project vs fresh scaffold

The skill's first action is always **discover**, not scaffold.

1. Locate the user's PBIR project (the `.pbip` folder or its `.Report`
   sibling).
2. Look for sibling directories matching `*-visual/` containing a
   `pbiviz.json`.
3. Branch:
   - **None found** → fresh scaffold flow (see "Plan-then-code" below).
   - **One found** → load it; jump to edit-validate-package-import loop.
   - **Multiple found** → ask the user which one; do not guess.

This means re-invoking the skill on day 2 picks up where day 1 left off.

## Plan-then-code (fresh scaffolds only)

For fresh scaffolds, before touching any code:

1. Read the user's natural-language spec.
2. Output a **5-line plan** stating:
   - Data roles needed (and which is `Grouping` vs `Measure`).
   - Primary render approach (DOM, SVG, D3, canvas, charting lib).
   - Formatting properties to expose in the right pane.
   - npm dependencies beyond the scaffold defaults (justify each one;
     see "npm dependency policy" below).
3. **Wait for explicit user OK** before generating code.

The plan exists to foreclose the most expensive failure mode: getting
the data role declarations wrong in `capabilities.json` and discovering
it 15 turns later. Skipping the plan is **not** an optimization.

For sustained-authoring edits to an **existing** project, skip the
plan step and iterate directly.

## Scaffold

Working directory: parent of the user's PBIR project (sibling, never
inside `.Report` or `.pbip`).

**Naming constraint:** `pbiviz new` rejects names containing anything
other than letters and digits. No hyphens, no underscores, no dots.
If the user's spec name has those (e.g. "my-gauge-visual"), strip them
before scaffolding (e.g. `mygaugevisual`). The friendly displayName in
`pbiviz.json` can still carry spaces and punctuation.

```bash
# Inside <project-parent>/
npx --yes powerbi-visuals-tools@^5.6.0 new <visualname>
cd <visualname>
```

### Auto-strip the circle-card demo

`pbiviz new` produces a working "circle card" demo. Strip it before
handing off to iteration:

1. Open `src/visual.ts`. The constructor and `update()` method contain
   demo-specific code (creates an `<svg>` with a `<circle>` and `<text>`
   that displays a number).
2. Replace the `update()` body with a **single comment** like
   `// TODO: build per spec` and remove the SVG/circle helpers.
3. Open `capabilities.json`. Replace the demo's `dataRoles` (typically
   `category` and `measure` for the circle demo) with the data roles
   you planned. Empty the `objects` block; add formatting properties
   per plan.
4. Open `style/visual.less`. Empty it.
5. Bump `apiVersion` in `pbiviz.json` and `powerbi-visuals-api` in
   `package.json` to the pinned version above (only if the scaffold
   doesn't already match).

### Fill required `pbiviz.json` metadata

`pbiviz package` strict-validates four fields and **fails to build**
if any are missing. The scaffold leaves them blank, so populate them
before any package step or you'll waste iteration turns chasing a
"not specified" error that has nothing to do with the code:

| Field                 | Source                                         |
|-----------------------|------------------------------------------------|
| `visual.description`  | One-line summary derived from the user's spec  |
| `visual.supportUrl`   | `"https://example.com"` placeholder (real URL needed for AppSource publish) |
| `author.name`         | `git config user.name` (run it; fall back to `"pbi-cli user"` if empty) |
| `author.email`        | `git config user.email` (run it; fall back to `"noreply@example.com"` if empty) |

Do this immediately after auto-stripping the circle-card demo, **once**,
in a single edit to `pbiviz.json`. If the user later wants to publish to
AppSource, they replace these values themselves; the skill's auto-bump
on `version` doesn't touch any other field, so user edits stick.

### Drop in AGENTS.md

Write `AGENTS.md` at the project root using the template at
`./AGENTS-template.md` (bundled alongside this `SKILL.md`). It tells
future-Claude what's editable, what's locked, and gives 30 lines of
SDK pattern crib. **Always drop this file.** It's the biggest single
lever on first-iteration success.

## Inner loop: agent-driven validation

For each user-requested change:

1. Edit the relevant files (see AGENTS.md for editable vs locked).
2. Run **fast** type check:

   ```bash
   npx tsc --noEmit -p tsconfig.json
   ```

3. If errors:
   - Read every error verbatim.
   - Fix them.
   - Re-run `tsc --noEmit`.
   - Repeat **with progress discipline** (see "Failure cap" below).
4. If clean: stop. Don't package every loop. Package only when the user
   asks "show me" or you're at a natural completion point.

Do **not** run `pbiviz start` in v1. The dev server's only purpose is
sub-second hot-reload while a human watches Desktop, which is not how
this loop works.

### Failure cap (self-policed)

To prevent infinite loops on cryptic SDK errors:

- **5-turn no-progress cap.** "Progress" = error count strictly decreased
  OR the qualitative root cause changed. After 5 turns with no progress,
  **stop**.
- **Oscillation detection.** If the same error appears, gets fixed, and
  reappears within 3 turns, **stop**.
- **On stop**: dump the current `tsc` output **verbatim** to the user
  and offer two concrete hypotheses for the root cause. Ask the user to
  guide. Do not silently keep trying.

## Package and import

When the inner loop is clean and you (or the user) are ready to see it:

```bash
# Bump patch version so Power BI Desktop's GUID+version cache invalidates
pbi-cli internal pbiviz-bump  # see note below

# Package
npx --yes powerbi-visuals-tools@^5.6.0 package
# .pbiviz lands in dist/<visualname>.<version>.pbiviz

# Import into the user's report
pbi visual import-custom dist/<visualname>.<version>.pbiviz --replace
```

**Version auto-bump.** Use the helper exposed via the skill (or call
`pbiviz_bump_patch()` from `pbi_cli.core.custom_visual_backend`
programmatically). It increments the patch number in `pbiviz.json`. If
the user has set a non-`<int>.<int>.<int>` version manually, the bump
is skipped and the manual value is respected.

**Why the bump matters.** Power BI Desktop caches custom visuals by
GUID + version. Repackaging without bumping risks Desktop serving stale
code on next open. The bump is cheap insurance.

If `pbiviz package` itself fails (capabilities schema invalid,
unsupported API features, etc.), feed the error to Claude the same way
as `tsc` errors. Subject to the same failure cap.

## Eyeball handoff

After a successful import, hand off to the user **once**:

> Imported. Open the report in Power BI Desktop, place the visual on a
> page, bind data, and tell me what looks wrong. I'll iterate from
> there.

If the user reports issues, repeat the inner loop (skip Plan since the
project exists). If the user reports it looks right, you're done.

## npm dependency policy

Custom visuals routinely need libraries (D3, Lodash, charting libs).
Installing arbitrary packages from npm is a real supply-chain concern
under the user's identity, so this skill operates under an **allowlist**.

### Allowlist (install without asking)

Any package in this list, at or above the version floor, may be
installed via `npm install <pkg>` (or `--save-dev`) without prior
user confirmation:

| Package                              | Floor       |
|--------------------------------------|-------------|
| `d3`                                 | `^7.0.0`    |
| `d3-array`                           | `^3.0.0`    |
| `d3-axis`                            | `^3.0.0`    |
| `d3-color`                           | `^3.0.0`    |
| `d3-format`                          | `^3.0.0`    |
| `d3-interpolate`                     | `^3.0.0`    |
| `d3-scale`                           | `^4.0.0`    |
| `d3-selection`                       | `^3.0.0`    |
| `d3-shape`                           | `^3.0.0`    |
| `d3-time-format`                     | `^4.0.0`    |
| `lodash`                             | `^4.17.0`   |
| `date-fns`                           | `^3.0.0`    |
| `powerbi-visuals-utils-formattingmodel` | `^6.0.0` |
| `powerbi-visuals-utils-tooltiputils` | `^6.0.0`    |
| `powerbi-visuals-utils-chartutils`   | `^6.0.0`    |
| `powerbi-visuals-utils-dataviewutils`| `^6.0.0`    |
| `@types/d3`                          | `^7.0.0`    |
| `@types/lodash`                      | `^4.14.0`   |

Type-only packages follow the same allowlist; there is no separate
`devDependencies` rule.

### Off-allowlist installs

Any package **not** on the allowlist (including any version below a
floor) requires **explicit user confirmation** before running
`npm install`. State all four:

1. **Package name** and exact version range.
2. **Why** it's needed for the user's spec (specific feature, not
   "it might be useful").
3. **Bundle size impact estimate** (k or kk gzipped; check
   bundlephobia.com if uncertain).
4. **Alternative** considered (an allowlisted package or hand-rolled
   code) and why it's worse.

Then wait for explicit "yes" / "go ahead". Don't proceed on silence.

This blocks the worst failure mode: hallucinating a typo'd package
name (e.g. `d3-scaling`) and installing typosquat malware. Typos won't
match the allowlist; the confirm step exposes them.

## Concrete CLI surface used by this skill

Provided by pbi-cli:

- `pbi visual import-custom <pbiviz-file> [--replace] [--no-sync]`
- `pbi visual list-custom`
- `pbi visual remove-custom <guid-or-name> [--no-sync]`

Provided by `npx --yes powerbi-visuals-tools@^5.6.0`:

- `new <name>` — scaffold project
- `package` — produce `.pbiviz` zip
- `--create-cert` — generate dev cert (only needed on first
  `package` invocation per machine; pbiviz prompts automatically)

That's the entire toolchain. No other commands needed in v1.

## What's deliberately out of scope (v1)

- `pbiviz start` live preview server.
- AppSource (public) custom visual registration via GUID-only
  (separate `register-public` command, deferred).
- Publishing to AppSource / Partner Center.
- Multi-visual workspaces, monorepo patterns.
- Custom visual themes / sharing across reports.

If the user asks for any of the above, surface that it's outside v1
scope and offer to file a follow-up.