frappe / frappe/studio

Upgrading to frappe-ui 1.0.0

Open
#225 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Vue
Stars
280
Forks
98
Avg merge
1d 30m
Merged PRs (30d)
11

Description

frappe-ui is heading to a `1.0.0` tag. The breaking changes land as separate PRs. This issue is the single place that tracks all of them for Studio.

- Work top to bottom. Each section names the exact sites in this repo, with before/after.
- More sections get appended as more breaks land before the tag.
- Do not start until you bump the frappe-ui pin. Today it is `1.0.0-beta.25` in `frontend/package.json`.
- Full list of changes: [migration guide](https://github.com/frappe/frappe-ui/blob/main/docs/content/docs/migration.md).

| # | Change | PR | Fails at build? |
| --- | --- | --- | --- |
| 1 | Sprite icons import from `frappe-ui/experimental` | frappe/frappe-ui#1002 | Yes |
| 2 | `Autocomplete` is deleted | frappe/frappe-ui#951 | One place yes, three no — including every generated app |
| 3 | `Popover` v0 API removed; `Tooltip` `placement` and `#body` renamed (7 Popover, 6 Tooltip) | frappe/frappe-ui#956 | No — every break is silent |

Not affected: frappe/frappe-ui#946 (Studio has no `useDoctype` or `useList`), frappe/frappe-ui#948 (Studio installs `resourcesPlugin`, not the `FrappeUI` plugin, and has its own `initSocket` in `frontend/src/socket.js`) and frappe/frappe-ui#949 (`frontend/src/utils/useScreenSize.ts` is Studio's own).

---

# 1. Sprite icons import from `frappe-ui/experimental`

PR: frappe/frappe-ui#1002 (ticket: frappe/frappe-ui#904)

This section used to describe frappe/frappe-ui#947, which removed the sprite trio. That PR is closed and did not ship. What shipped instead: `spritePlugin`, `IconPicker` and the sprite `Icon` moved from `frappe-ui/icons` to `frappe-ui/experimental`.

## Fix now: change the import path

Props, emits and behavior are unchanged. Tailwind styles keep working: `frappe-ui/tailwind` now globs the new location, so an app that spreads its `content` needs no config change.

```ts
// before
import { spritePlugin, IconPicker, Icon } from 'frappe-ui/icons'

// after
import { spritePlugin, IconPicker, Icon } from 'frappe-ui/experimental'
```

Six sites:

- `frontend/src/main.ts:10` — `spritePlugin`
- `frontend/src/renderer.ts:9` — `spritePlugin`
- `frontend/cypress/component/drop-components.cy.ts:7` — `spritePlugin`
- `frontend/src/scripts/build.js:203` — `spritePlugin`, inside the template string Studio writes into a generated app's entry file. Generated apps need the new path too.
- `frontend/src/utils/globalUtils.ts:3` — `Icon` (registered globally)
- `frontend/src/components/ArrayInput.vue:100` — `IconPicker`

If a file also imports the root `Icon` from `frappe-ui`, alias the sprite one:

```ts
import { Icon as SpriteIcon } from 'frappe-ui/experimental'
```

The named SFC icons on `frappe-ui/icons` did not move.

## Plan for later: this surface goes away

`frappe-ui/experimental` is a parking lot. The trio is still slated for removal, after 1.0.0. Plan to migrate icon rendering to `lucide-*` classes or the root `Icon` component. The removal, with the `lucide-` prefix and safelist work this section described before, will land as its own section here when it has a PR.

---

# 2. `Autocomplete` is deleted

PR: frappe/frappe-ui#951

`Autocomplete` is gone. `Combobox` replaces the single-select form, `MultiSelect` the multiple one. `FormControl type="autocomplete"` is gone with it.

Four places in Studio, across five files. Only one is a build error. Start with 2.1 — it is the only one that reaches users of apps Studio built.

Line numbers are against `develop` as of 2026-08-08.

## 2.1 Every generated app gets a broken import — Studio's own build stays green

`frontend/src/utils/constants.js:3` lists `"Autocomplete"` in `FRAPPE_UI_COMPONENTS`:

```js
export const FRAPPE_UI_COMPONENTS = [
"Alert",
"Autocomplete", // ← delete this line
"Avatar",
```

`frontend/src/scripts/build.js:124` routes any component named in that array into `frappeUIComponents`, and `:157-158` writes it straight into the generated app's entry file:

```js
const frappeUIImports =
frappeUIComponents.length > 0 ? `import { ${frappeUIComponents.join(",\n ")} } from "frappe-ui";` : ""
```

So any saved Studio app holding an `Autocomplete` block emits `import { Autocomplete, … } from "frappe-ui"` into its renderer. That import no longer resolves. The generated app fails to build.

Studio itself will not tell you. The name is a string in an array and the import is a template literal — nothing type-checks either one. The failure surfaces at a downstream app's build, not Studio's.

Do three things:

1. Drop `"Autocomplete"` from `FRAPPE_UI_COMPONENTS`. `"Combobox"` is already in the list at `:11`.
2. Migrate the stored blocks. `Studio Page.blocks` and `Studio Page.draft_blocks` hold the block tree as JSON; any block whose component is `Autocomplete` needs to become `Combobox`, and any `{ group, items }` option groups on it need to become `{ group, options }`. Left alone, the block is dropped from the import list and renders as an unregistered component.
3. Regenerate `frontend/src/json_types/` — `frontend/src/scripts/tsToJSONGenerator.ts` reads frappe-ui's types, and `AutocompleteProps` is no longer there. `frontend/src/json_types/index.ts:3` and `frontend/src/json_types/frappeui/Autocomplete.json` go with it.

`frontend/src/types/doctype.ts:35` also lists `"Autocomplete"` as a `FieldTypes` member. That one is Frappe's Docfield fieldtype, not the component — keep it, but see 2.4 for where it leaks into `FormControl`.

## 2.2 `DynamicValueSelector.vue` — build error, then two silent breaks behind it

`frontend/src/components/DynamicValueSelector.vue:48`:

```ts
// before
import { Autocomplete, Switch, Tooltip } from "frappe-ui"
// after
import { Combobox, Switch, Tooltip } from "frappe-ui"
```

The build stops there. Three more things in the same file change meaning, and none of them fail to compile.

**The five option groups use `items`.** `Combobox` reads `options`, and throws naming the group and the key as soon as the popover opens. Lines `:96/97`, `:104/105`, `:113/114`, `:132/133` and `:149/150`:

```js
// before
groups.push({
group: "Component Inputs",
items: componentContext,
})
// after
groups.push({
group: "Component Inputs",
options: componentContext,
})
```

**`#target` is `#trigger`, and it wires the click itself.** Lines `:10-30`:

```vue

```

`togglePopover` no longer exists, so leaving the handler on throws on click — but the popover still opens, because `ComboboxAnchor` already handled the click. Drop the handler.

**`v-model` gives back the value, not the option.** Line `:8`:

```vue

@update:modelValue="(option: VariableOption) => emit('update:modelValue', option.value, bindVariable)"

@update:modelValue="(value: string) => emit('update:modelValue', value, bindVariable)"
```

`option.value` becomes `undefined` — no error, the emit just carries nothing. If you need the whole option, listen to `@update:selectedOption` instead.

Two smaller ones in the same file:

- `:32` `#item-suffix="{ option }"` — the item slot prop is `item` now. `option.type` becomes `undefined` and the row suffix renders empty.
- `:6` `placement="left-start"` — `Combobox` takes `side` and `align`: `side="left" align="start"`.

`#footer` (`:35`) carries over unchanged.

## 2.3 `ResourceDialog.vue` — silent, and it renders a plain text box

`frontend/src/components/ResourceDialog.vue:136-142`, on frappe-ui's `FormControl` (imported at `:200`):

```vue

```

`type="autocomplete"` is no longer a `FormControl` type. It falls through to a text input and passes the type on, so you get `` — a plain text box, no build error and no runtime error. `:multiple="true"` means the replacement is `multiselect`, not `combobox`; `MultiSelect` is multiple by definition, so drop the prop.

This one fixes a data-shape mismatch that is already in the code. `whitelisted_methods` is declared `string[]` (`frontend/src/types/Studio/StudioResource.ts:23`), is loaded as a string array (`ResourceDialog.vue:272`), and is consumed as a string array (`frontend/src/stores/codeStore.ts:596`). But the old `Autocomplete` wrote back option objects, which is why `frontend/src/components/DataPanel.vue:232` runs the value through `getAutocompleteValues()` (`frontend/src/utils/helpers.ts:298`) to unwrap it. `MultiSelect` binds values, so the whole path becomes `string[]` end to end and that unwrap becomes a no-op.

## 2.4 `Grid.vue` passes a Docfield fieldtype straight through to `FormControl` — silent

`frontend/src/components/Grid.vue:71-73` lowercases the column's fieldtype and hands it to frappe-ui's `FormControl` (imported at `:102`):

```vue
`
- `frontend/src/data/components.ts:448` — a `condition` on `state.type === "autocomplete"`

Leave all of them alone. Changing them to `combobox` would break working code.

Migration guide section: "Selection family (Dropdown / Select / Combobox / MultiSelect)", plus "`FormControl type=\"autocomplete\"`".

---

Replaces #224.

---

# 3. Popover and Tooltip: the v0 API is gone

PR: frappe/frappe-ui#956

frappe-ui 1.0.0 removes the v0 `Popover` API and renames two things on `Tooltip`. Studio has 13 affected sites.

Nothing warns. Vue drops an unknown prop or slot in silence, so a missed site renders a popover with no trigger, an empty one, or a tooltip on the wrong edge — and the build stays green. Work the list.

Line numbers are against `develop` at `dc6a43c`.

## Popover

| v0 | v1 |
| --- | --- |
| `#target` slot | `#trigger` — it wires its own click, so drop the handler |
| `#body` slot | `#default` slot **plus** the `bare` prop — `#body` rendered outside the panel shell |
| `#body-main` slot | `#default` slot |
| `togglePopover` slot prop | `toggle` |
| `isOpen` slot prop | `open` |
| `placement="bottom-end"` | `side="bottom"` + `align="end"` (a bare `placement="bottom"` is `align="center"`) |
| `show` / `v-model:show` / `@update:show` | `open` / `v-model:open` / `@update:open` |
| `popoverClass` | `[data-slot="content"]` in CSS |
| `trigger="hover"` | the `HoverCard` component |
| `transition` | gone — motion is built in |

```vue











```

The leftover click handler is the one that bites. `#trigger` already toggles, so keeping your own `@click` toggles twice and the popover never opens.

`#body` and `#body-main` are not the same slot. `#body-main` rendered inside the panel, so it is plain `#default`. `#body` replaced the panel, so it needs `bare` as well — without it your content lands inside a second panel.

```vue

```

7 sites:

- `frontend/src/components/AIChatPanel.vue:169` — #target slot, #body slot, placement prop, togglePopover slot prop.
- `frontend/src/components/ColorPicker.vue:3` — #target slot, #body slot, placement prop, popoverClass, transition prop, isOpen slot prop, togglePopover slot prop.
- `frontend/src/components/ComponentInterface.vue:19` — #target slot, #body-main slot, placement prop, show prop / update:show.
- `frontend/src/components/InlineInput.vue:18` — #target slot, #body slot, placement prop, trigger prop.
- `frontend/src/components/PageScript.vue:20` — #target slot, #body slot, placement prop, togglePopover slot prop.
- `frontend/src/components/SearchBlock.vue:26` — #target slot, #body slot, isOpen slot prop, togglePopover slot prop.
- `frontend/src/components/StudioToolbar.vue:76` — #target slot, #body slot, placement prop, popoverClass, transition prop, isOpen slot prop, togglePopover slot prop.

## Tooltip

| v0 | v1 |
| --- | --- |
| `placement="top"` | `side="top"` |

`#default` stays the trigger. That inversion is deliberate and is not changing.

6 sites:

- `frontend/src/components/ComponentProperties.vue:36` — placement prop.
- `frontend/src/components/FileExplorer.vue:99` — placement prop.
- `frontend/src/components/IconButton.vue:2` — placement prop.
- `frontend/src/components/PagesPanel.vue:10` — placement prop.
- `frontend/src/components/PagesPanel.vue:23` — placement prop.
- `frontend/src/components/StudioLeftPanel.vue:7` — placement prop.

Full before/after for all of it: [migration guide](https://github.com/frappe/frappe-ui/blob/main/docs/content/docs/migration.md#popover--hovercard--tooltip).

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by reading the migration guide and bumping the frappe-ui pin in frontend/package.json. Work through the three sections and the named files, including generated-app handling, component API changes, and stored block data. Done means all listed imports and APIs are migrated, generated apps build, and the affected Studio controls retain their expected behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, typescript
Domain
frontend
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.