Skip to content

Kody is live

Watch the launch video — what Kody is, and why it exists.

← Public packages

@kentcdodds/package-app-kit

Design tokens, PWA install/update, About/version, cache helpers, and optional realtime notes sync for Kody package apps.

starter-fetch/AGENTS.template.md

71 lines · 3.3 KB · Markdown

PACKAGE_NAME — agent notes

Scaffolded from @kentcdodds/package-app-kit/starter. Human Intent lives in README.

  • Handoff: private hosted package apps need Open Package App from kody.codes; installed PWAs can show a cached shell while assets 403 if the session cookie expired — reopen from the package page.

Recipe (Cole #2284)

"kody": {
  "app": {
    "entry": "./src/app.ts",
    "client": { "entry": "./src/client/index.ts", "externals": ["@remix-run/ui"] },
    "assets": "./public"
  }
}
  • Worker src/app.ts — fetch handler only. Never import src/client/index.ts (separate graphs).
  • Client src/client/index.ts — TypeScript browser entry; platform bundles to /_assets/client.<hash>.js.
  • Assets public/ — static files at /_assets/… (includes sw.js).

HTML stamps data-app-base, data-client-module, data-asset-base, data-pak-config and loads:

<script type="module" src="{packageContext.clientModuleUrl}"></script>

Dual-path (until platform ships everywhere): when clientModuleUrl is null, fall back to Worker-served kit /client/boot.js. Documented interim only.

Imports (kit Worker helpers)

import themeCss from 'kody:@kentcdodds/package-app-kit/styles'
import { clientBootResponse } from 'kody:@kentcdodds/package-app-kit/client' // interim fallback only
import recordVersion from 'kody:__PACKAGE_NAME__/record-version'
import { formatAboutPageData, aboutPageHtml } from 'kody:@kentcdodds/package-app-kit/about'
import { installChromeHtml } from 'kody:@kentcdodds/package-app-kit/install'
import { defaultManifestIcons, iconPngResponse } from 'kody:@kentcdodds/package-app-kit/icons'
import { renderServiceWorker, buildWebManifest } from 'kody:@kentcdodds/package-app-kit/sw'

Browser client must not import kody: / kody:@…. Keep @remix-run/ui as client.externals + import map → esm.sh.

Service worker

Register ${assetBasePath}/sw.js with { scope: appBasePath } when assets are live. public/sw.js reads /_assets/__version.json for clientModuleUrl (no hash in SW source). Interim: Worker /sw.js via renderServiceWorker.

Installability

PNG icons 192 + 512 via kit iconPngResponse at /icons/…. Manifest linked with crossorigin="use-credentials". Capture beforeinstallprompt + preventDefault; hide Install until BIP; iOS A2HS only on iOS UA.

Optional L3 — Realtime

Package apps can add live sync with kit ./realtime + ./realtime-client (see kit AGENTS L3 — Realtime). Typical recipe: export handleRealtimeEvent from app/router.ts, broadcast after durable writes via KodyRuntime.realtime, connect from a browser island with connectPackageRealtime. Do not wire WebSocket into every scaffold by default — adopt when the product needs multi-session notify (Notes-style).

Smoke

import { kody } from 'kody:runtime'
export default async function main() {
  const paths = ['/', '/about', '/manifest.webmanifest', '/icons/icon-192.png', '/icons/icon-512.png', '/sw.js']
  const out = []
  for (const path of paths) {
    const res = await kody.packageAppFetch({ kody_id: '__PACKAGE_ID__', path })
    out.push({ path, status: res.status, type: res.headers?.['content-type'] || res.headers?.['Content-Type'] })
  }
  return out
}