frappe / frappe/central

Upgrading to frappe-ui 1.0.0

Open
#254 1 comment 1 reaction 0 assignees View on GitHub
Dominant language
Python
Stars
18
Forks
11
Avg merge
1d 8h
Merged PRs (30d)
20

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 Central.

- 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 `github:frappe/frappe-ui#v1.0.0-beta.24` in `dashboard/package.json:15`.
- 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 | `$socket` and the `FrappeUI` plugin options | frappe/frappe-ui#948 | No — the whole signed-in dashboard throws at runtime |
| 2 | `useTheme` is `useColorScheme` | frappe/frappe-ui#949 | Yes |
| 3 | `Autocomplete` is deleted | frappe/frappe-ui#951 | No — two silent |
| 4 | `Popover` v0 API removed; `Tooltip` `placement` and `#body` renamed (1 Popover) | frappe/frappe-ui#956 | No — every break is silent |
| 5 | Dropdown `placement`, `{ group, items }` and `component:` rows removed (8 sites) | frappe/frappe-ui#957 | No — silent; `vue-tsc` flags them |

Do #1 first. It takes the app down.

Not affected: frappe/frappe-ui#946 (Central has no `useDoctype` and no `useList` — its 91 `useCall` instantiations across 40 files are untouched by that PR, which changed only `useAction.ts`, `useDoctype.ts` and `useList.ts`) and frappe/frappe-ui#947 (no `spritePlugin`, no `frappe-ui/icons` import, no `IconPicker`).

Line numbers are against `develop` as of 2026-08-08 (`138750e3d`).

---

# 1. The plugin no longer opens a socket, and reading `$socket` throws

PR: frappe/frappe-ui#948

**This blanks the entire signed-in dashboard.** Not one widget — every authenticated route.

## 1.1 What happens

`dashboard/src/main.ts:16-18` asks the plugin for a socket:

```ts
app.use(FrappeUI, {
socketio: window.socketio_port ? { port: window.socketio_port } : true,
})
```

`socketio` is no longer an option. The plugin ignores it (with a dev-only `console.warn`) and opens nothing. `initSocket` is deleted too.

`dashboard/src/composables/common/useFrappeRealtime.ts:177-190` then reads the global the plugin used to set:

```ts
function useFrappeSocket(): FrappeSocket {
const instance = getCurrentInstance()
const socket = instance?.appContext.config.globalProperties.$socket as // line 179
| FrappeSocket
| undefined

if (!socket) { // line 183
throw new Error(
'Frappe socket is unavailable. Call this composable inside setup() after installing FrappeUI.',
)
}

return socket
}
```

The plugin now installs a **throwing getter** on `globalProperties.$socket`, so line `:179` throws before line `:183` ever runs. Central's own guard and its own error message are dead code. What surfaces instead is frappe-ui's message.

## 1.2 Why the whole app goes

`dashboard/src/layouts/AppShell.vue:26` calls `useNotificationsRealtime()` at the top level of ``:

```ts
useNotificationsRealtime()
```

That reaches `useFrappeEventListener` (`useNotifications.ts:40`), which calls `useFrappeSocket()` on line `:47` of `useFrappeRealtime.ts`. The read throws during setup, so `AppShell` never renders.

`AppShell` is the parent route component for every authenticated route (`dashboard/src/router/index.ts:44`). Signed-in users get a blank page.

Three more entry points throw the same way once you get past that one:

| Feature | Chain |
| --- | --- |
| Notification bell | `AppShell.vue:26` → `useNotifications.ts:40` → `useFrappeEventListener` (`useFrappeRealtime.ts:47`) |
| Servers list | `useServers.ts:5,58` → `useFrappeList.ts:94` → `useFrappeListInvalidation` (`useFrappeRealtime.ts:172` → `useFrappeDocTypeEventListener` `:128`) |
| Server map | `useServerMapData.ts:4,50` → same `useFrappeListInvalidation` |

## 1.3 The fix

Open the connection yourself and assign it over the guard. Assigning still works — the plugin's getter has a matching setter, and it does not clobber a value assigned before install.

`dashboard/package.json` does not depend on `socket.io-client` today (it came in through frappe-ui). Add it:

```
yarn add socket.io-client
```

`dashboard/src/main.ts`:

```ts
// before
app.use(FrappeUI, {
socketio: window.socketio_port ? { port: window.socketio_port } : true,
})

// after
import { io } from 'socket.io-client'

app.use(FrappeUI)

const host = window.location.hostname
const siteName = import.meta.env.DEV ? host : window.site_name
const socketioPort = window.socketio_port || 9000
const port = window.location.port ? `:${socketioPort}` : ''
const protocol = port ? 'http' : 'https'
app.config.globalProperties.$socket = io(
`${protocol}://${host}${port}/${siteName}`,
{ withCredentials: true },
)
```

That is what `initSocket` did, line for line. `window.site_name` and `window.socketio_port` are both already declared (`dashboard/src/env.d.ts:17-18`) and both are injected by `central/www/dashboard.py:31-32`.

Then delete the dead guard. `useFrappeRealtime.ts:183-187` can no longer fire — a missing `$socket` throws at the read. Either drop the `if (!socket)` block, or read through a `try` if you want Central's own message back:

```ts
function useFrappeSocket(): FrappeSocket {
const instance = getCurrentInstance()
const globals = instance?.appContext.config.globalProperties
let socket: FrappeSocket | undefined
try {
socket = globals?.$socket as FrappeSocket | undefined
} catch {
socket = undefined
}
if (!socket) {
throw new Error('Frappe socket is unavailable. Assign app.config.globalProperties.$socket in main.ts.')
}
return socket
}
```

## 1.4 Nothing else in #948 touches Central

- `request` is not imported anywhere. `dashboard/src/composables/useAuth.ts:1` and `dashboard/src/lib/auth.ts:1` already use `frappeRequest`.
- `createCall` is never imported from frappe-ui. The `createCall` identifiers in `useTeamSettings.ts:33` and `useServers.ts:105` are local `useCall` instances that happen to share the name.
- No component declares an Options-API `resources: {}` block, so the `$resources` opt-in does not apply. Do **not** pass `{ resources: true }`.
- No `config: {}` option is passed, so there is nothing to move to `setConfig`.

---

# 2. `useTheme` is `useColorScheme`

PR: frappe/frappe-ui#949

Build error at the import line, then four dangling identifiers behind it.

## 2.1 The wrapper

`dashboard/src/composables/useTheme.ts:1` and `:13`:

```ts
// before
import { useTheme as useFrappeUITheme } from 'frappe-ui'

export function useTheme() {
if (!localStorage.getItem('theme')) {
localStorage.setItem('theme', 'light')
}
return useFrappeUITheme()
}

// after
import { useColorScheme } from 'frappe-ui'

export function useTheme() {
if (!localStorage.getItem('theme')) {
localStorage.setItem('theme', 'light')
}
const { colorScheme, setColorScheme, toggleColorScheme } = useColorScheme()
return {
currentTheme: colorScheme,
setTheme: setColorScheme,
toggleTheme: toggleColorScheme,
}
}
```

Mapping the names inside the wrapper keeps the three consumers below unchanged. Rename them through if you prefer the new vocabulary — it is four lines either way.

The storage key (`theme`) and the `<html data-theme>` attribute are unchanged by #949, so the seeding on `:10-12` keeps working and users keep their saved preference.

## 2.2 The consumers

Members become `{ colorScheme, setColorScheme, toggleColorScheme }`. Central reads the old names in three files:

- `dashboard/src/composables/useAppMenu.ts:15` — `const { currentTheme, setTheme } = useTheme()`, then `:36`, `:37`, `:59`, `:60`
- `dashboard/src/pages/settings/SettingsPage.vue:10-11`, `:47`, `:48`
- `dashboard/src/components/search/index.ts:50`, `:86` — reads `setTheme` off `useAppMenu()`, so it needs no change if you keep the wrapper's shape

## 2.3 `colorScheme` is read-only

Nothing in Central assigns to `currentTheme.value` today, so this costs nothing — but do not add one. The old ref moved only itself and left `<html>` and `localStorage` behind. Use `setColorScheme`.

## 2.4 The rest of #949 does not apply

No scroll composables. No frappe-ui directives. `dashboard/src/composables/common/useIsMobile.ts` is Central's own — the removed export was frappe-ui's `useIsMobile`, which Central never imported.

---

# 3. `Autocomplete` is deleted

PR: frappe/frappe-ui#951

Two sites, both silent. `FormControl type="autocomplete"` falls through to a plain text input and passes the type on, so you get `<input type="autocomplete">` — no build error, no runtime error, just a text box where the picker used to be.

`dashboard/src/components/billing/EditBillingProfileDialog.vue`, on frappe-ui's `FormControl` (imported at `:2`).

Country, `:188-194`:

```vue
<!-- before -->
<FormControl
v-model="countryModel"
type="autocomplete"
label="Country *"
placeholder="Select country"
:options="countryOptions"
/>

<!-- after -->
<FormControl
v-model="form.country"
type="combobox"
label="Country *"
placeholder="Select country"
:options="countryOptions"
/>
```

State, `:195-202`: the same change, with `v-model="form.state"`.

## 3.1 Delete the `optionModel` wrapper

`Combobox` binds the option's **value**, not the option object. The old `Autocomplete` bound the object, which is the only reason `:83-91` exists:

```ts
function optionModel(field: string) {
return computed<{ label: string; value: string } | null>({
get: () => (form[field] ? { label: form[field], value: form[field] } : null),
set: (opt) => {
form[field] = opt?.value ?? ''
},
})
}
const countryModel = optionModel('country') // line 92
const stateModel = optionModel('state') // line 93
```

Bind `form.country` and `form.state` straight through and delete lines `:83-93`. `form.country` is already a plain string — `:74-81` watches it and `:69` compares it to `'India'`.

`countryOptions` (`:65-67`) is already `{ label, value }[]`, which `Combobox` takes unchanged. `stateOptions` (`:68`) is a plain `string[]`, which it also takes.

## 3.2 What is not affected

Every other `autocomplete` in `dashboard/src` is the plain HTML attribute (`autocomplete="off"`, `autocomplete="email"`). There is no `import { Autocomplete }` anywhere and no local `Autocomplete.vue` fork.

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

---

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

PR: frappe/frappe-ui#956

frappe-ui 1.0.0 removes the v0 `Popover` API. Central has 1 affected site.

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 `10f4c5d`.

## Popover

| v0 | v1 |
| --- | --- |
| `#body` slot | `#default` slot **plus** the `bare` prop — `#body` rendered outside the panel shell |
| `placement="bottom-end"` | `side="bottom"` + `align="end"` (a bare `placement="bottom"` is `align="center"`) |

`#body` replaced the panel shell, so it maps to `#default` **plus** `bare` — without `bare` your content lands inside a second panel.

```vue
<!-- before -->
<Popover>
<template #body><EmojiPicker /></template>
</Popover>

<!-- after -->
<Popover bare>
<EmojiPicker />
</Popover>
```

1 site:

- `dashboard/src/components/notifications/NotificationBell.vue:31` — #body slot, 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).

---

# 5. Dropdown: `placement`, `{ group, items }` and `component:` rows are gone

PR: frappe/frappe-ui#957

frappe-ui 1.0.0 removes three shapes from `Dropdown` (`ContextMenu` shares the option types). Central has 8 affected sites.

Nothing fails the build. A `placement` prop is dropped and the menu shifts to the default alignment; a `{ group, items }` group resolves to zero options and renders an empty menu; a `component:` row renders as a plain action row using its `label`, which for most of these rows is empty. Dev builds log a console warning for the last two, and the removed keys stay in the types as `never`, so `vue-tsc` names every site. Work the list.

Line numbers are against `develop` at `10f4c5d`.

## `placement` → `align`

| before | after |
| --- | --- |
| `placement="right"` | `align="end"` |
| `placement="center"` | `align="center"` |
| `placement="left"` | delete the prop — `start` is the default |

`PaymentMethodRowActions.vue` and `SubscriptionRowActions.vue` pass `bottom-end`, a value `placement` never accepted (`'left' | 'right' | 'center'`) — it has been silently falling through to start-aligned all along. Delete the prop to keep what renders today, or write `align="end"` for what the site meant — that one changes what users see.

8 sites:

- `dashboard/src/components/addons/AIApiKeys.vue:223` — `right`.
- `dashboard/src/components/billing/PaymentMethodRowActions.vue:60` — `bottom-end — see note above`.
- `dashboard/src/components/billing/SubscriptionRowActions.vue:56` — `bottom-end — see note above`.
- `dashboard/src/components/servers/ServerRowActions.vue:100` — `right`.
- `dashboard/src/components/servers/SiteRowActions.vue:40` — `right`.
- `dashboard/src/components/team/InvitationRowActions.vue:37` — `right`.
- `dashboard/src/components/team/RoleRowActions.vue:29` — `right`.
- `dashboard/src/components/team/TeamMemberRowActions.vue:37` — `right`.

Full before/after: [migration guide](https://github.com/frappe/frappe-ui/blob/main/docs/content/docs/migration.md#dropdown-and-contextmenu).

Contributor guide

No contributing guide indexed for this repository

Research direction

Read the migration guide and dashboard/package.json:15 first, then inspect dashboard/src/main.ts and the listed composables and components section by section. Work from the frappe-ui 1.0.0 breaking-change list, run the dashboard build and vue-tsc checks, and verify authenticated routes and affected controls still render without silent API failures.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.