diff --git a/alias.ts b/alias.ts index d80bc99c..c1deaabc 100644 --- a/alias.ts +++ b/alias.ts @@ -36,7 +36,6 @@ export const alias = { 'devframe/utils/structured-clone': r('devframe/src/utils/structured-clone.ts'), 'devframe/utils/when': r('devframe/src/utils/when.ts'), 'devframe/adapters/cac': r('devframe/src/adapters/cac.ts'), - 'devframe/adapters/cli': r('devframe/src/adapters/cli.ts'), 'devframe/adapters/dev': r('devframe/src/adapters/dev.ts'), 'devframe/adapters/build': r('devframe/src/adapters/build.ts'), 'devframe/helpers/vite': r('devframe/src/helpers/vite.ts'), @@ -80,7 +79,6 @@ export const alias = { '@devframes/plugin-git': p('git/src/index.ts'), 'devframe/recipes/interactive-auth': r('devframe/src/recipes/interactive-auth.ts'), 'devframe/recipes/common-rpc-functions': r('devframe/src/recipes/common-rpc-functions.ts'), - 'devframe/recipes/open-helpers': r('devframe/src/recipes/open-helpers.ts'), 'devframe/client': r('devframe/src/client/index.ts'), 'devframe': r('devframe/src'), '@devframes/plugin-data-inspector/client': p('data-inspector/src/client/index.ts'), diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index aa06f91d..2c37dd44 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -146,6 +146,8 @@ export function devframeNav(prefix = ''): DefaultTheme.NavItem[] { { text: 'Contributing', link: `${repo}/blob/main/CONTRIBUTING.md` }, { items: [ + { text: 'Migrating to 0.9', link: `${prefix}/guide/migration-0.9` }, + { text: 'Migrating to 0.8', link: `${prefix}/guide/migration-0.8` }, { text: 'Migrating to 0.7', link: `${prefix}/guide/migration-0.7` }, { text: 'Migrating to 0.6', link: `${prefix}/guide/migration-0.6` }, ], diff --git a/docs/adapters/cac.md b/docs/adapters/cac.md index ea4f0ed4..810f58ad 100644 --- a/docs/adapters/cac.md +++ b/docs/adapters/cac.md @@ -28,8 +28,6 @@ const devframe = defineDevframe({ await createCac(devframe).parse() ``` -The `devframe/adapters/cli` entry (`createCli`) remains as a deprecated alias for this module — new code should import `createCac` from `devframe/adapters/cac`. - Running the resulting binary: ```sh diff --git a/docs/guide/migration-0.8.md b/docs/guide/migration-0.8.md new file mode 100644 index 00000000..dc1bb4ab --- /dev/null +++ b/docs/guide/migration-0.8.md @@ -0,0 +1,78 @@ +--- +outline: deep +--- + +# Migrating to 0.8 + +0.8 makes RPC schemas validator-neutral and validated at runtime, upgrades the MCP adapter to `@modelcontextprotocol/sdk` v2, and lands the agent-native MCP surface (`ctx.agent`, tool providers, and the `devframe connect` connector). This page covers the changes between 0.7.x and 0.8 — see the [v0.8.0 release notes](https://github.com/devframes/devframe/releases/tag/v0.8.0) for the full changelog. + +## RPC schemas are Standard Schema and validated at runtime + +`args` and `returns` on `defineRpcFunction` are now typed against [Standard Schema](https://standardschema.dev/) rather than valibot's `GenericSchema`. Any Standard-Schema-compliant validator works — valibot, zod, arktype — so existing valibot schemas keep compiling and inferring types unchanged. + +Two things change in practice: + +**Devframe no longer bundles a validator.** `valibot` was dropped from `devframe`'s runtime dependencies, so author your schemas with whichever validator you prefer and install it yourself: + +```sh +npm install valibot # or: zod / arktype +``` + +If your app already pulls in zod (the JSON-render integration and the MCP server both use it), prefer zod for your RPC schemas and reuse the dependency. For first-party code that wants zero dependencies, devframe ships a minimal built-in builder at `devframe/utils/simple-schema`: + +```ts +// Bring your own validator … +import { defineRpcFunction } from 'devframe' +import * as v from 'valibot' + +export const rename = defineRpcFunction({ + name: 'devframes:plugin:terminals:rename', + type: 'action', + args: [v.object({ id: v.string(), title: v.string() })], + returns: v.void(), + setup: ctx => ({ handler: ({ id, title }) => { /* … */ } }), +}) +``` + +```ts +// … or use the built-in zero-dep builder +import { defineRpcFunction } from 'devframe' +import { s } from 'devframe/utils/simple-schema' + +export const rename = defineRpcFunction({ + name: 'devframes:plugin:terminals:rename', + type: 'action', + args: [s.object({ id: s.string(), title: s.string() })], + returns: s.void(), + setup: ctx => ({ handler: ({ id, title }) => { /* … */ } }), +}) +``` + +**Declared schemas are now enforced.** Each argument is validated against its schema at the boundary before the handler runs, and the resolved return value is validated on the way out; a mismatch is rejected with a coded diagnostic instead of reaching (or leaving) the handler. Payloads are guarded, not rewritten — extra object fields the schema doesn't mention still reach the handler. Audit any schema that was previously more of a type hint than a contract, since inputs that used to slip through now throw. + +See [RPC](./rpc) for the full reference. + +## MCP adapter upgraded to `@modelcontextprotocol/sdk` v2 + +The MCP adapter now targets the v2 SDK, which ships as scoped `@modelcontextprotocol/server` and `@modelcontextprotocol/client` packages. Swap the peer dependency when you ship MCP support: + +| 0.7.x | 0.8 | +|-------|-----| +| `@modelcontextprotocol/sdk@^1` | `@modelcontextprotocol/server@^2` | + +```sh +npm uninstall @modelcontextprotocol/sdk +npm install @modelcontextprotocol/server +``` + +`@modelcontextprotocol/server` remains an optional peer dependency, pulled in only through `devframe/adapters/mcp`. The `devframe connect` connector (below) additionally uses `@modelcontextprotocol/client` — install it too if you drive agents through the connector. + +If you imported SDK types directly, the deep `@modelcontextprotocol/sdk/...` subpaths are now flat entries on the scoped packages (e.g. `@modelcontextprotocol/sdk/server/stdio.js` → `@modelcontextprotocol/server/stdio`). See [MCP](/adapters/mcp) for the adapter reference. + +## Agent-native MCP surface + +0.8 adds the agent host on `ctx.agent` — `registerTool`, `registerToolProvider`, and `registerResource` — plus an instance registry and the `devframe connect` MCP connector shipped in the `devframe` bin. These are additive; existing `agent`-flagged RPCs keep working and are projected to MCP as before. + +One type sharpens: an RPC `handler` (and its `dump`) now returns `Thenable` — the `returns` schema describes the *resolved* value and the runtime always awaits the handler. Synchronous handlers are unaffected; an `async` handler whose declared `returns` was the unwrapped value now type-checks correctly rather than needing the promise spelled into the schema. + +See [Agent-Native](./agent-native) for the tool/resource surface and [MCP → `devframe connect`](/adapters/mcp#discovery-devframe-connect) for the connector. diff --git a/docs/guide/migration-0.9.md b/docs/guide/migration-0.9.md new file mode 100644 index 00000000..de70f882 --- /dev/null +++ b/docs/guide/migration-0.9.md @@ -0,0 +1,98 @@ +--- +outline: deep +--- + +# Migrating to 0.9 + +0.9 removes the compatibility shims that were deprecated across the 0.7 series. Each removed export has a drop-in replacement that has shipped alongside it since 0.7, so migrating is a matter of updating import paths and a handful of call sites. This page covers the changes between 0.8.x and 0.9. + +## `devframe/adapters/cli` is removed + +The CLI adapter was renamed to `cac` in 0.7. The `devframe/adapters/cli` entry — `createCli`, `CreateCliOptions`, and `CliHandle` — is now gone. Import from `devframe/adapters/cac` instead: + +| 0.8.x | 0.9 | +|-------|-----| +| `import { createCli } from 'devframe/adapters/cli'` | `import { createCac } from 'devframe/adapters/cac'` | +| `CreateCliOptions` | `CreateCacOptions` | +| `CliHandle` | `CacHandle` | + +```ts +// 0.8.x +import { createCli } from 'devframe/adapters/cli' + +await createCli(devframe).parse() +``` + +```ts +// 0.9 +import { createCac } from 'devframe/adapters/cac' + +await createCac(devframe).parse() +``` + +The typed-flag helpers `defineCliFlags` and `parseCliFlags` live on `devframe/adapters/cac` too. See [CLI (cac)](/adapters/cac) for the full adapter reference. + +## `devframe/recipes/open-helpers` is removed + +The recipe was renamed to `common-rpc-functions` in 0.7.16. Import `commonRpcFunctions` from `devframe/recipes/common-rpc-functions`: + +```ts +// 0.8.x +import { openHelpers } from 'devframe/recipes/open-helpers' +``` + +```ts +// 0.9 +import { commonRpcFunctions } from 'devframe/recipes/common-rpc-functions' +``` + +The `openInEditor` and `openInFinder` members are unchanged. See [Common RPC functions](/helpers/common-rpc-functions) for the full reference. + +## RPC dump re-exports move to `devframe/rpc/dump` + +The static-dump helpers and types are served from the dedicated `devframe/rpc/dump` entry; the aliases that re-exported them from the top-level `devframe/rpc` barrel are removed. Import them from `devframe/rpc/dump`: + +```ts +// 0.8.x +import { createClientFromDump, dumpFunctions } from 'devframe/rpc' + +// 0.9 +import { createClientFromDump, dumpFunctions } from 'devframe/rpc/dump' +``` + +This applies to every dump export — `collectStaticRpcDump`, `createClientFromDump`, `dumpFunctions`, `getDefinitionsWithDumps`, `reviveDumpError`, `serializeDumpError`, and the `StaticRpcDump*` types. + +## `@devframes/hub` json-render shims are removed + +json-render moved out of the hub into the opt-in [`@devframes/json-render`](./json-render) integration in 0.7. The hub-local compatibility shims are now removed: the `defineJsonRenderSpec` helper, the `ctx.createJsonRenderer` factory, the `DevframeViewJsonRender` dock type, and the `JsonRenderSpec` / `JsonRenderElement` / `JsonRenderer` types. + +| 0.8.x (`@devframes/hub`) | 0.9 (`@devframes/json-render`) | +|---|---| +| `defineJsonRenderSpec(spec)` | Pass the spec directly to `createJsonRenderView(ctx, { id, spec })` | +| `ctx.createJsonRenderer(spec)` | `createJsonRenderView(ctx, { id, spec })` (from `@devframes/json-render/node`) | +| `JsonRenderSpec` | `DevframeJsonRenderSpec` | +| `JsonRenderElement` | element shape of `DevframeJsonRenderSpec` | +| `JsonRenderer` | `JsonRenderView` | +| `DevframeViewJsonRender` | `DevframeJsonRenderDockEntry` (from `@devframes/json-render/hub`) | + +```ts +// 0.8.x +import { defineJsonRenderSpec } from '@devframes/hub' + +const spec = defineJsonRenderSpec({ root: 'panel', elements: { /* ... */ } }) +const renderer = ctx.createJsonRenderer(spec) +``` + +```ts +// 0.9 +import { createJsonRenderView } from '@devframes/json-render/node' + +const view = createJsonRenderView(ctx, { + id: 'panel', + spec: { root: 'panel', elements: { /* ... */ } }, +}) +``` + +`createJsonRenderView` returns a view carrying a serializable `ref` (a shared-state key or an inline spec). Project it onto a hub dock with `toJsonRenderDockEntry` from `@devframes/json-render/hub`, which contributes the `'json-render'` dock type to the hub's open dock union. A dock entry now carries that serializable `view` ref rather than a live renderer handle — a client reads `entry.view.stateKey` (or `entry.view.spec`) to render it. + +See [JSON-Render](./json-render) for the full integration reference. diff --git a/docs/helpers/common-rpc-functions.md b/docs/helpers/common-rpc-functions.md index 8efa0d43..f9ce70d4 100644 --- a/docs/helpers/common-rpc-functions.md +++ b/docs/helpers/common-rpc-functions.md @@ -30,8 +30,6 @@ defineDevframe({ Both functions are `action`-type RPCs returning `void`, and their arguments are schema-validated — `openInEditor`'s `editor` argument is restricted to `KNOWN_EDITORS`, so a value outside that list fails validation rather than reaching the underlying `launch-editor` process spawn. Both handlers dynamically `import()` their underlying `devframe/utils/*` implementation, so the `launch-editor` and `open` dependencies only load when the recipe actually runs. -The `devframe/recipes/open-helpers` entry (`openHelpers`) remains as a deprecated alias for this module — new code should import `commonRpcFunctions` from `devframe/recipes/common-rpc-functions`. - ## Pick and choose Register only the helper you need rather than the whole array: diff --git a/knip.jsonc b/knip.jsonc index 38de700d..35ccf6be 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -73,12 +73,12 @@ "entry": [ "src/{index,constants}.ts", "src/helpers/vite.ts", - "src/adapters/{build,cac,cli,dev,embedded,initiate}.ts", + "src/adapters/{build,cac,dev,embedded,initiate}.ts", "src/adapters/mcp/index.ts", "src/client/index.ts", "src/node/index.ts", "src/node/{auth,hub-internals}/index.ts", - "src/recipes/{common-rpc-functions,interactive-auth,open-helpers}.ts", + "src/recipes/{common-rpc-functions,interactive-auth}.ts", "src/rpc/{index,client,server}.ts", "src/rpc/dump/index.ts", "src/rpc/transports/{ws-bun,ws-client,ws-server}.ts", diff --git a/packages/devframe/package.json b/packages/devframe/package.json index fec54b16..4f71c1ac 100644 --- a/packages/devframe/package.json +++ b/packages/devframe/package.json @@ -22,7 +22,6 @@ ".": "./dist/index.mjs", "./adapters/build": "./dist/adapters/build.mjs", "./adapters/cac": "./dist/adapters/cac.mjs", - "./adapters/cli": "./dist/adapters/cli.mjs", "./adapters/dev": "./dist/adapters/dev.mjs", "./adapters/embedded": "./dist/adapters/embedded.mjs", "./adapters/mcp": "./dist/adapters/mcp.mjs", @@ -35,7 +34,6 @@ "./node/hub-internals": "./dist/node/hub-internals.mjs", "./recipes/common-rpc-functions": "./dist/recipes/common-rpc-functions.mjs", "./recipes/interactive-auth": "./dist/recipes/interactive-auth.mjs", - "./recipes/open-helpers": "./dist/recipes/open-helpers.mjs", "./rpc": "./dist/rpc/index.mjs", "./rpc/client": "./dist/rpc/client.mjs", "./rpc/dump": "./dist/rpc/dump.mjs", diff --git a/packages/devframe/src/adapters/cac.ts b/packages/devframe/src/adapters/cac.ts index a513e220..88556099 100644 --- a/packages/devframe/src/adapters/cac.ts +++ b/packages/devframe/src/adapters/cac.ts @@ -6,9 +6,6 @@ // without it throws at load time with the usual Node module-not-found // error. The typed-flag helpers (`defineCliFlags` / `parseCliFlags`) are // re-exported below so they live alongside the CLI adapter. -// -// The historical `devframe/adapters/cli` entry (`createCli`) re-exports -// this module under deprecated aliases for backward compatibility. import type { CAC } from 'cac' import type { H3 } from 'h3' import type { DevframeDefinition } from '../types/devframe' diff --git a/packages/devframe/src/adapters/cli.ts b/packages/devframe/src/adapters/cli.ts deleted file mode 100644 index bebadd05..00000000 --- a/packages/devframe/src/adapters/cli.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Deprecated compatibility shim for the cac adapter. -// -// Prefer the canonical `devframe/adapters/cac` entry (`createCac`). This -// module re-exports the same implementation under the historical `cli` -// names so existing imports keep working. It will be removed in a future -// major release. -import type { CacHandle, CreateCacOptions } from './cac' -import { createCac } from './cac' - -export { defineCliFlags, parseCliFlags } from './flags' -export type { CliFlagsSchema, InferCliFlags } from './flags' - -/** @deprecated Use `createCac` from `devframe/adapters/cac` instead. */ -export const createCli: typeof createCac = createCac - -/** @deprecated Use `CreateCacOptions` from `devframe/adapters/cac` instead. */ -export type CreateCliOptions = CreateCacOptions - -/** @deprecated Use `CacHandle` from `devframe/adapters/cac` instead. */ -export type CliHandle = CacHandle diff --git a/packages/devframe/src/recipes/__tests__/common-rpc-functions.test.ts b/packages/devframe/src/recipes/__tests__/common-rpc-functions.test.ts index 37b21f37..9057c434 100644 --- a/packages/devframe/src/recipes/__tests__/common-rpc-functions.test.ts +++ b/packages/devframe/src/recipes/__tests__/common-rpc-functions.test.ts @@ -1,7 +1,6 @@ import type { StandardSchemaV1 } from '@standard-schema/spec' import { describe, expect, it } from 'vitest' import { commonRpcFunctions, KNOWN_EDITORS, openInEditor, openInFinder } from '../common-rpc-functions' -import { openHelpers } from '../open-helpers' /** Synchronously check whether a value satisfies a Standard Schema. */ function accepts(schema: StandardSchemaV1, value: unknown): boolean { @@ -42,8 +41,4 @@ describe('recipes/common-rpc-functions', () => { expect(commonRpcFunctions).toContain(openInEditor) expect(commonRpcFunctions).toContain(openInFinder) }) - - it('keeps the deprecated `devframe/recipes/open-helpers` entry working as an alias', () => { - expect(openHelpers).toBe(commonRpcFunctions) - }) }) diff --git a/packages/devframe/src/recipes/open-helpers.ts b/packages/devframe/src/recipes/open-helpers.ts deleted file mode 100644 index 85c18ed7..00000000 --- a/packages/devframe/src/recipes/open-helpers.ts +++ /dev/null @@ -1,12 +0,0 @@ -// Deprecated compatibility shim for the open helpers recipe. -// -// Prefer the canonical `devframe/recipes/common-rpc-functions` entry. This -// module re-exports the same implementation under the historical name so -// existing imports keep working. It will be removed in a future major -// release. -import { commonRpcFunctions } from './common-rpc-functions' - -export { openInEditor, openInFinder } from './common-rpc-functions' - -/** @deprecated Use `commonRpcFunctions` from `devframe/recipes/common-rpc-functions` instead. */ -export const openHelpers: typeof commonRpcFunctions = commonRpcFunctions diff --git a/packages/devframe/src/rpc/dump/__tests__/dump.test.ts b/packages/devframe/src/rpc/dump/__tests__/dump.test.ts index b203c360..7ba194c1 100644 --- a/packages/devframe/src/rpc/dump/__tests__/dump.test.ts +++ b/packages/devframe/src/rpc/dump/__tests__/dump.test.ts @@ -1,7 +1,8 @@ import type { RpcDumpRecord } from '../../types' import * as v from 'valibot' import { describe, expect, it } from 'vitest' -import { createClientFromDump, createDefineWrapperWithContext, defineRpcFunction, dumpFunctions } from '../..' +import { createClientFromDump, dumpFunctions } from '..' +import { createDefineWrapperWithContext, defineRpcFunction } from '../..' describe('dumps', () => { it('should collect dumps from definition', async () => { diff --git a/packages/devframe/src/rpc/index.ts b/packages/devframe/src/rpc/index.ts index 00448b0d..a96716ba 100644 --- a/packages/devframe/src/rpc/index.ts +++ b/packages/devframe/src/rpc/index.ts @@ -1,25 +1,3 @@ -import type { - StaticRpcDumpCollection as _StaticRpcDumpCollection, - StaticRpcDumpFile as _StaticRpcDumpFile, - StaticRpcDumpManifest as _StaticRpcDumpManifest, - StaticRpcDumpManifestQueryEntry as _StaticRpcDumpManifestQueryEntry, - StaticRpcDumpManifestStaticEntry as _StaticRpcDumpManifestStaticEntry, - StaticRpcDumpManifestValue as _StaticRpcDumpManifestValue, - StaticRpcDumpSerialization as _StaticRpcDumpSerialization, -} from './dump/static' -import { - createClientFromDump as _createClientFromDump, - dumpFunctions as _dumpFunctions, - getDefinitionsWithDumps as _getDefinitionsWithDumps, -} from './dump' -import { - reviveDumpError as _reviveDumpError, - serializeDumpError as _serializeDumpError, -} from './dump/error' -import { - collectStaticRpcDump as _collectStaticRpcDump, -} from './dump/static' - export * from './cache' export * from './collector' export * from './define' @@ -28,31 +6,3 @@ export * from './serialization' export * from './types' export * from './validate-io' export * from './validation' - -/** @deprecated Import from `devframe/rpc/dump` instead. */ -export const collectStaticRpcDump = _collectStaticRpcDump -/** @deprecated Import from `devframe/rpc/dump` instead. */ -export const createClientFromDump = _createClientFromDump -/** @deprecated Import from `devframe/rpc/dump` instead. */ -export const dumpFunctions = _dumpFunctions -/** @deprecated Import from `devframe/rpc/dump` instead. */ -export const getDefinitionsWithDumps = _getDefinitionsWithDumps -/** @deprecated Import from `devframe/rpc/dump` instead. */ -export const reviveDumpError = _reviveDumpError -/** @deprecated Import from `devframe/rpc/dump` instead. */ -export const serializeDumpError = _serializeDumpError - -/** @deprecated Import from `devframe/rpc/dump` instead. */ -export type StaticRpcDumpCollection = _StaticRpcDumpCollection -/** @deprecated Import from `devframe/rpc/dump` instead. */ -export type StaticRpcDumpFile = _StaticRpcDumpFile -/** @deprecated Import from `devframe/rpc/dump` instead. */ -export type StaticRpcDumpManifest = _StaticRpcDumpManifest -/** @deprecated Import from `devframe/rpc/dump` instead. */ -export type StaticRpcDumpManifestQueryEntry = _StaticRpcDumpManifestQueryEntry -/** @deprecated Import from `devframe/rpc/dump` instead. */ -export type StaticRpcDumpManifestStaticEntry = _StaticRpcDumpManifestStaticEntry -/** @deprecated Import from `devframe/rpc/dump` instead. */ -export type StaticRpcDumpManifestValue = _StaticRpcDumpManifestValue -/** @deprecated Import from `devframe/rpc/dump` instead. */ -export type StaticRpcDumpSerialization = _StaticRpcDumpSerialization diff --git a/packages/devframe/tsdown.config.ts b/packages/devframe/tsdown.config.ts index e1c79dbe..1f35911f 100644 --- a/packages/devframe/tsdown.config.ts +++ b/packages/devframe/tsdown.config.ts @@ -103,7 +103,6 @@ const serverEntries = { 'utils/open': 'src/utils/open.ts', 'utils/serve-static': 'src/utils/serve-static.ts', 'adapters/cac': 'src/adapters/cac.ts', - 'adapters/cli': 'src/adapters/cli.ts', 'adapters/dev': 'src/adapters/dev.ts', 'adapters/build': 'src/adapters/build.ts', 'adapters/embedded': 'src/adapters/embedded.ts', @@ -112,7 +111,6 @@ const serverEntries = { 'cli/main': 'src/cli/main.ts', 'helpers/vite': 'src/helpers/vite.ts', 'recipes/common-rpc-functions': 'src/recipes/common-rpc-functions.ts', - 'recipes/open-helpers': 'src/recipes/open-helpers.ts', 'recipes/interactive-auth': 'src/recipes/interactive-auth.ts', } diff --git a/packages/hub-ui/package.json b/packages/hub-ui/package.json index 3253f234..6ccd9f78 100644 --- a/packages/hub-ui/package.json +++ b/packages/hub-ui/package.json @@ -41,11 +41,13 @@ }, "peerDependencies": { "@devframes/hub": "workspace:*", + "@devframes/json-render": "workspace:*", "devframe": "workspace:*" }, "devDependencies": { "@antfu/design": "catalog:frontend", "@devframes/hub": "workspace:*", + "@devframes/json-render": "workspace:*", "@iconify-json/ph": "catalog:frontend", "@json-render/core": "catalog:deps", "@json-render/vue": "catalog:frontend", diff --git a/packages/hub-ui/src/client/components/views/ViewJsonRender.vue b/packages/hub-ui/src/client/components/views/ViewJsonRender.vue index 47755a3f..39f83164 100644 --- a/packages/hub-ui/src/client/components/views/ViewJsonRender.vue +++ b/packages/hub-ui/src/client/components/views/ViewJsonRender.vue @@ -1,31 +1,35 @@