casset/docs
FeaturesOpen app
docs indexreference
00Overview01Thesis02Architecture03Casset Apps04System reality05Roadmap06Investor brief07Technical brief08Full tech HTML09API reference10Playback11Audio pipeline12Commerce13Base anchoring14Hook system15Music video16Theming17Creator guide18Glossary
LearnQuickstartApp modelSDKStartersReferenceDistribution

Casset Apps · Quickstart

0.1 alpha · local development

Run a host-connected Profile App in five minutes.

Download the React starter, edit one validated manifest, and rehearse the interface against deterministic Casset host fixtures. The starter is real; production installation and third-party submission are not open.

Step 1

Download and run the React Profile App.

Terminalshell
curl -L https://casset.fm/downloads/casset-apps/casset-react-profile-app-v0.1.0-alpha.0.zip \
  -o casset-react-profile-app-v0.1.0-alpha.0.zip
unzip casset-react-profile-app-v0.1.0-alpha.0.zip
cd casset-react-profile-app
pnpm install
pnpm dev

Repository-local dependency

The ZIP vendors @casset/apps at vendor/casset-apps-sdk and rewrites the dependency to a local file path. Do not run pnpm add @casset/apps; the package is not published to npm.

Step 2

Edit the public manifest.

The starter imports this JSON and calls parseCassetAppManifest during startup. Unknown fields, invalid permissions, and illegal context combinations fail loudly.
casset-app.jsonjson
{
  "schema": "casset.app-manifest.v1",
  "id": "studio.release-letter",
  "slug": "release-letter",
  "name": "Release Letter",
  "shortDescription": "An intimate release artifact for an artist's living Profile World.",
  "description": "Release Letter stages a host-approved release and sends narrow playback requests back to Casset.",
  "developer": { "name": "Your studio" },
  "version": "0.1.0",
  "icon": "/icon.svg",
  "screenshots": [],
  "category": "RELEASE",
  "context": "PROFILE_SURFACE",
  "placements": ["MEDIA_FOOTER", "ARTIST_APP_ROUTE"],
  "permissions": [
    "artist.profile.read",
    "artist.theme.read",
    "catalog.tracks.read",
    "catalog.releases.read",
    "playback.read",
    "playback.control"
  ],
  "supportedFanAccess": ["FREE"],
  "compatibility": { "mobile": true, "desktop": true, "pwa": true },
  "trackApplicability": null
}

Step 3

Build one room, not another shell.

Host supplied

Read bounded context

Use artist identity, safe theme values, approved tracks, and the one active release only when their data states are ready.

Profile World

Keep navigation native

Do not add global navigation, authentication, checkout, or a replacement MediaFooter.

Sparse states

Treat absence as authored

Loading, empty, unavailable, permission-denied, missing artwork, and a closed host are normal states.

Step 4

Ask the host to play an approved Track.

A Profile App may request PLAY_TRACK only when both the permission and host capability are present. Casset still decides whether the request is accepted.
Guarded host requesttsx
import {
  hasCassetPermission,
  hostSupports,
  isCassetDataReady,
  type CassetAppHost,
} from "@casset/apps"

export async function askCassetToPlay(host: CassetAppHost, trackId: string) {
  const snapshot = host.getSnapshot()
  if (snapshot.context.kind !== "PROFILE_SURFACE") return
  if (!isCassetDataReady(snapshot.context.catalog)) return
  if (!hasCassetPermission(snapshot.context, "playback.control")) return
  if (!hostSupports(host, "PROFILE_PLAY_TRACK")) return
  if (!snapshot.context.catalog.data.tracks.some((track) => track.id === trackId)) return

  return host.request({ type: "PLAY_TRACK", trackId })
}

One playback authority

Never add an audio element, Web Audio graph, playback store, interval, or RAF clock. Track Runtime is read-only in this alpha and exposes no playback command bridge.

Step 5

Subscribe once. Clean up completely.

The host can update context or close the App. Every mount must release its subscription and local listeners on deactivation.
Lifecycle cleanupts
const render = (snapshot: CassetHostSnapshot) => {
  // Render only the latest host-approved snapshot.
}

const unsubscribe = host.subscribe(render)
render(host.getSnapshot())

return () => {
  unsubscribe()
  root.replaceChildren()
}

Finish

Exercise the states that production will enforce.

  • Use the fixture selector for loading, ready, empty, unavailable, and permission-denied context.
  • Test accepted, denied, and unsupported PLAY_TRACK decisions.
  • Inspect 320×568, 390×844, and desktop widths; then test keyboard-only input and reduced motion.
  • Call host.close() and confirm the App releases subscriptions and stops reacting.
  • Treat disablement and uninstallation as loss of future host access; a local fixture cannot prove server authorization.
Validate the startershell
pnpm test
pnpm build

Next

Read the App model, inspect the SDK, or compare all three starter kits.
© Casset 2026
Trust & PrivacyTerms