@juuno-sdk/app-sdk
v3.8.0
Published
Juuno App SDK - The contract package for building external Juuno apps
Readme
@juuno-sdk/app-sdk
The contract package for building external Juuno apps
Overview
@juuno-sdk/app-sdk is the contract package for developing external Juuno apps, with one entry per host surface (ADR 0031):
@juuno-sdk/app-sdk/player— what a scene's player bundle may import.@juuno-sdk/app-sdk/config— the config UI surface, media library and i18n included.@juuno-sdk/app-sdk/preview— the static preview surface.
The package is a pure re-export shim: it bundles no copies. At runtime the Juuno hosts serve every re-exported package through their import maps, so an app externalises the SDK and ships none of its code. The root export is the union of the three entries, kept for tooling that resolves the bare package name.
Contract version
This package's major.minor version is the player-app contract version (ADR 0007; refined by ADR 0030, landing with the release-resolution work). The contract surface is everything this package re-exports: today that spans @juuno/apps-player-common, @juuno/apps-core, @juuno/apps-config-common, @juuno/apps-slideshow-common, @juuno/media-library, @juuno/i18n-common, and selected members of @juuno/core, @juuno/components, @juuno/icons, @juuno/types, @juuno/common, and @juuno/utils. The shared Vue instance is a peer dependency, not a re-export: its range in package.json is the record of it. Any change to that surface must bump this package's version, including changes made in those source packages without any file in this folder appearing in the diff:
- Major: a breaking change. Majors are epochs. Release resolution never serves an app release across a major boundary, and every first-party app re-releases at the new major in the same PR.
- Minor: an additive change (a new component, prop, or capability). Minors are floors. A release stamped
2.1is served only to players implementing minor1or later within major2. - Patch: no contract meaning. Free for fixes that change no surface.
Your app declares the contract version it supports as a required sdk_version field in src/manifest.json, written as major.minor:
{
"id": "my-app",
"version": "1.0.4",
"sdk_version": "2.0"
}The two version fields are unrelated. version is your app's own release number and carries no compatibility meaning; sdk_version is the SDK surface you build against. A defaulted value would be a floor nobody declared, so it is required rather than inferred.
Write the version your app is built and tested against, and update it when you adopt surface added in a newer SDK. Nothing compares your declaration against the SDK you actually compiled with, so an understated floor is served to players that will fail on it. The player derives and advertises the version it implements from the SDK it compiled against (FE#2079), and the backend records your declaration on the release and rejects a deployment without it (juuno-co/juuno-backend#658).
Where the field is enforced depends on how you build. Apps built in the Juuno monorepo fail at build time, because the manifest plugin validates it there. A third-party app built with your own tooling is checked when you deploy, by the CLI and then by the backend. From 2.0.0 this package also exports its package.json, so tooling can read the SDK version through module resolution.
This package deliberately re-exports nothing from vue. Apps import vue directly: it is a peer of this package, every app externalises it, and the host import map serves the one shared instance. The contract version lives in package.json only.
The surface snapshot
type-surface.player.d.ts, type-surface.config.d.ts and type-surface.preview.d.ts are generated records of everything each entry exports, resolved through the re-exports so the shapes are the ones an app actually compiles against. The root union is deliberately not snapshotted: it would double every diff. CI regenerates it, fails if the committed copy is stale, and fails again if it moved on a pull request whose package.json version did not. That is what makes the bump duty enforceable for a change made in a source package, where nothing under external/app-sdk/ appears in the diff at all.
When CI asks for it, regenerate and commit alongside the bump:
pnpm sdk:surfaceIt type-checks first, because the extractor reads the declarations vue-tsc emits rather than the source. Expect a diff whenever a re-exported component gains a prop or an exported type changes shape. What it cannot see is a behaviour change behind an unchanged signature, which stays reviewer judgment, as in any library.
Installation
npm install @juuno-sdk/app-sdkPeer Dependencies
This package requires:
{
"peerDependencies": {
"vue": "^3.5.22"
}
}What's Included
- Config UI Framework: Components and utilities for building app configuration interfaces
- Player Utilities: Components and helpers for rendering app content
- UI Components: Buttons, modals, inputs, color pickers, and more
- Icons: Common icon components (SvgAdd, SvgClose, SvgDelete, etc.)
- Styles: none to import; the hosts serve every component's CSS
- Types: TypeScript types for all APIs
- Utilities: Helper functions (createLogger, cloneDeep, etc.)
Usage
Basic App Structure
// src/player/AppSlide.vue
<script setup lang="ts">
import {
type AppSlideProps,
createLogger
} from '@juuno-sdk/app-sdk/player';
const Logger = createLogger('MyApp');
const props = defineProps<AppSlideProps>();
</script>
<template>
<div class="my-app">
<!-- Your app content -->
</div>
</template>Config UI Example
// src/config/AppConfig.vue
<script setup lang="ts">
import { useAppConfig, ColorPickerInput } from '@juuno-sdk/app-sdk/config';
type MyAppMeta = {
backgroundColor: string;
};
const config = useAppConfig<MyAppMeta>();
// config.meta is the scene's meta, seeded from defaultMeta; the host saves it.
</script>
<template>
<div class="config">
<ColorPickerInput
v-model="config.meta.backgroundColor"
label="Background Color"
/>
</div>
</template>Styles
The SDK ships no stylesheet. Every component the entries re-export is served by the host, and the host page carries that component CSS, so an app imports nothing.
Available Exports
Config UI
useAppConfigContext<T>()- Access app config contextprovideAppConfigContext()- Provide config contextAppConfigContainer- Container for config UISelectTextPosition- Text position selectorWysiwygEditor- WYSIWYG editor componentuseDirtyState()- Track unsaved changes
The full per-entry lists
The generated snapshots are the authoritative record of what each entry exports, with the resolved shapes an app compiles against:
type-surface.player.d.ts— what@juuno-sdk/app-sdk/playerexportstype-surface.config.d.ts— what@juuno-sdk/app-sdk/configexportstype-surface.preview.d.ts— what@juuno-sdk/app-sdk/previewexports
A hand-written list here would drift; this one cannot, because CI regenerates the snapshots and fails when the committed copies are stale.
Build Configuration
Externalize Vue and every @juuno-sdk/app-sdk/* specifier; the host provides
both at runtime:
rollupOptions: {
external: ['vue', /^@juuno-sdk\/app-sdk(\/.*)?$/],
},The full vite.config.ts, with one input per manifest resource and the
static-copy step, is in SKILL.md step 4.
What's NOT Included
For external apps, the following features are not available:
- ❌ Font Management: Use Google Fonts or custom web fonts
- ❌ GraphQL/Backend Integration: External apps are self-contained; the host's data layer is not part of the contract (ADR 0031 decision 5)
The media library input (InputImage) and i18n (useI18n) are part of the
config entry.
Getting Started
SKILL.md, shipped in this package, is the step-by-step guide:
creating an app, publishing a new version, and moving onto a newer contract
version. It is written for an agent to follow, and it carries the preflight
checks and the failure modes this README only describes.
Read it before you start. The short version:
- Ask Juuno to create your App record. This is not self-serve yet, and nothing deploys until it exists.
npm i @juuno-sdk/app-sdk vueandnpm i -D vite @vitejs/plugin-vue @juuno-sdk/cli.- Write
manifest.json, anicon.svg, and arender()entry each for player and config. npx juuno-cli deploy, then confirmnpx juuno-cli info <your-app-id>prints a manifest carrying the version you just deployed.
create-juuno-appis not published yet. When it ships it must seedsdk_version, since a scaffold that omits it produces an app that cannot deploy.
TypeScript Configuration
External apps need type stubs for SVG and GraphQL files that may be imported from SDK dependencies:
// types/svg.d.ts
declare module '*.svg?component' {
import { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}
declare module '*.svg?skipsvgo' {
import { Component } from 'vue';
const src: string & Component;
export default src;
}
// types/graphql.d.ts
declare module '*.graphql' {
import { DocumentNode } from 'graphql';
const document: DocumentNode;
export default document;
}Then configure tsconfig.json to exclude internal packages and skip lib checking:
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["src/**/*", "src/**/*.vue", "types/**/*"],
"exclude": [
"node_modules/@juuno/core/**/*",
"node_modules/@juuno/icons/**/*"
],
"compilerOptions": {
"skipLibCheck": true,
"skipDefaultLibCheck": true
}
}Related Packages
- @juuno-sdk/cli: Development tool for testing and deploying external apps
SKILL.md: Step-by-step lifecycle guide shipped in this package
License
MIT
