# __PACKAGE_NAME__ — agent notes
Scaffolded from `@kentcdodds/package-app-kit/starter-tanstack`. Human Intent lives in README.
- **Handoff:** open private hosted apps with **Open Package App** from kody.codes; re-open from the package page when you need a fresh session or service worker.
## After scaffolding
After `create-package-app-tanstack` lands this tree (or you copy `starter-tanstack/`), work through this checklist before treating the app as product-ready:
1. **List mark (TanStack default included)** — This scaffold ships the square TanStack koala mark at `.kody/icon.png` / `.kody/icon.svg` (SVG wraps the PNG for text-only `packageSave`). Keep it unless the product needs its own brand — then replace with a distinctive square mark (do not reuse the kit demo race-driver). Prefer PNG on the Artifacts git lane. This is package / repo / community list identity.
2. **PWA + in-app chrome marks (TanStack default included)** — Favicon / header / PWA icons already default to the same TanStack koala via `src/icon-png-bytes.ts` and `/icons/icon-192.png` + `/icons/icon-512.png` routes (git lane also has matching `icons/*.png`). Replace those bytes/files only when product-branding. Serve from **app routes** and point favicon / header `
` at `routes.…href()`. For a hero or alternate bust, add the same kind of route (for example `/brand/logo-full.png`). Keep list identity in `.kody/`; keep `public/` for CSS, `sw.js`, and other asset-host files.
3. **Fill product identity** — Set `__APP_TITLE__` / title, description, and `packageName` (and related placeholders) to the real product names.
4. **Keep local version writers** — Use this package's `src/version.ts` + `src/record-version.ts` against **this** app's `packageStorage`. Call **this** package's `./record-version` (`kody:__PACKAGE_NAME__/record-version`), not the kit's export.
5. **Record each publish** — After every successful publish, call this package's `./record-version` with the published SHA (and message / `committedAt` when available).
6. **TanStack recipe ([#2312](https://github.com/kody-hq/kody/pull/2312))** — Confirm root `tsconfig.json` has `"jsx": "react-jsx"` + `"jsxImportSource": "react"`; `app/router.ts` remounts the Request and `export default { fetch }`; `app/routeTree.tsx` builds `@tanstack/react-router` routes; SSR uses `react-dom/server.edge` `renderToReadableStream` in `app/ssr.tsx` (**no** TanStack Start / Vite); client `app/assets/entry.ts` calls `hydrateRoot` + `RouterProvider`.
7. **Match manifest colors to the mark** — Set `themeColor` / `backgroundColor` in `buildWebManifest` (see `app/controllers/manifest.ts`) and the document `theme-color` meta so they harmonize with the mark.
8. **Optional UI icons** — Add Lucide glyphs via `node scripts/add-lucide-icon.mjs ` into `app/icons/` when the UI needs icon-only or labeled controls.
Then smoke with `packageAppFetch` (paths in **Smoke** below).
## Kody CLI workflow
This package ships npm scripts around the Kody CLI (`@kodycodes/cli`, pinned in `devDependencies`, so run `npm install` once). Each script is a thin wrapper; the raw command is listed so agents without npm (or with only MCP) can follow along.
| Step | Script | Raw command |
| --- | --- | --- |
| Sign in + git access (humans) | `npm run setup` | `kody login && kody auth setup-git` |
| Pull | — | `git clone https://kody.codes/__PACKAGE_NAME__.git` then `git pull` |
| Run locally (mock) | `npm run dev` | `kody dev --target mock` |
| Run against real data | `npm run dev:prod` | `kody dev --target prod` |
| Fetch one path | `npm run fetch -- /health` | `kody dev fetch /health` |
| Run a job once | `npm run job -- ` | `kody dev job ` |
| Send a webhook | `npm run webhook -- --body fixtures/webhook.example.json` | `kody dev webhook --body ` |
| Emit an event | `npm run emit -- --body fixtures/event.example.json` | `kody dev emit --body ` |
| Bundle + publish checks | `npm run check` | `kody dev check` |
| Types | `npm run typecheck` | `tsc --noEmit` |
| Tests | `npm test` | `node --test` (smoke tests call `kody dev fetch` on mock) |
| Push | — | `git push` |
| Publish | `npm run publish:kody` (add `-- ` if your CLI rejects the name) | tests, `node scripts/prepublish-check.mjs`, then `kody package publish ` |
Loop: pull, edit, `npm run dev` (or `npm run fetch -- `), `npm test`, commit, `git push`, `npm run publish:kody`. Add `--dry-run` to `job` / `webhook` / `emit` to see what would run without side effects. Mock is the default target: scratch storage and fake credentials, so unpublished code never touches real data. Use `dev:prod` only when you mean to.
### Signing in (explicit token lifetimes)
- **Humans:** `npm run setup` (browser `kody login`, then `kody auth setup-git`, which defaults to `--lifetime long`).
- **Agents on a task:** get a one-shot `kody_bc_…` code from MCP `cliCredentialBootstrap`, then `npx kody auth bootstrap --code --lifetime short` (1h idle / 24h max). Only use `--lifetime long` (14d idle / 3mo max) for a long-running agent the owner set up on purpose. Then `npx kody auth setup-git --lifetime short` for git access.
- Always pass `--lifetime` explicitly. Never print, log, commit, or paste tokens. Do not call `kody api cliCredentialBootstrapRedeem` (it prints the token). `kody status` / `kody whoami` confirm sign-in without showing it.
- Package code reads Kody **credentials** through the runtime, never from files in this repo. `scripts/prepublish-check.mjs` refuses to publish if a tracked file looks like it contains one.
### Without a shell (MCP-only agents)
Scripts are optional. The same loop works through Kody MCP tools: edit files with `packageSave` (or repo sessions), publish with `packagePublishExternalPush`, and smoke with `packageAppFetch` (see **Smoke** below). Before publishing, re-read **After scaffolding** and confirm README/AGENTS still describe the app.
## TanStack recipe (framework-agnostic host)
```json
"kody": {
"app": {
"entry": "./app/router.ts",
"client": "./app/assets/entry.ts",
"assets": "./public"
}
}
```
- **tsconfig** — root `tsconfig.json` with `"jsx": "react-jsx"` and `"jsxImportSource": "react"`.
- **Router** `app/router.ts` — remount the Request for `appBasePath`, then `export default { fetch(request) { … } }` dispatching API routes + `renderApp` SSR.
- **Route tree** `app/routeTree.tsx` — `@tanstack/react-router` `createRootRouteWithContext` + page routes; `basepath` matches the mount.
- **SSR** `app/ssr.tsx` — `createMemoryHistory` + `renderToReadableStream()` (Workers-safe; **not** TanStack Start).
- **Routes helpers** `app/routes.ts` — `createRoutes(appBasePath)` for hrefs (no `kody:runtime` in this module).
- **Client** `app/assets/entry.ts` — `hydrateRoot(document, )` + SW register.
- **Assets** `public/` — `styles.css`, `sw.js` (reads `__version.json`; no content hash in source).
## Imports — React + TanStack Router (no Vite / Start)
```ts
import {
createRouter,
createRootRouteWithContext,
createRoute,
createMemoryHistory,
createBrowserHistory,
RouterProvider,
Link,
} from '@tanstack/react-router'
import { renderToReadableStream } from 'react-dom/server.edge'
import { hydrateRoot } from 'react-dom/client'
import { packageContext, packageStorage } from 'kody:runtime'
```
**Forbid:**
- TanStack Start / Vite / Pitlane tooling inside the Worker publish
- `remix/component` JSX pragmas (this starter is standard React)
- Importing `kody:` / `kody:@…` from the browser client graph
## Kit (Worker / SSR only)
```ts
import { formatAboutPageData } from 'kody:@kentcdodds/package-app-kit/about'
import { buildWebManifest } from 'kody:@kentcdodds/package-app-kit/sw'
import { starterManifestIcons, iconPngResponse } from '../src/icon-png-bytes.ts'
```
**Version metadata:** use this package's local `./record-version` (`src/record-version.ts` → this app's `packageStorage`). Do not re-export or call `kody:@kentcdodds/package-app-kit/record-version` from execute — that writes the kit's storage and fails provenance. About / `/api/version` import `readRecordedVersion` relatively from `../../src/record-version.ts`.
Browser islands must **not** import `kody:` / `kody:@…`.
## List / identity mark
Put the package list mark at `.kody/icon.png` (also `.svg` / `.webp` / `.jpg` / `.jpeg`). This scaffold includes the **TanStack koala** mark (`.kody/icon.png` + `.kody/icon.svg` base64 wrapper) — keep it unless replacing with the product's official logo (prefer PNG on the git lane). Do **not** add root `community-icon.*`. Do **not** move PWA icons (`icons/icon-192.png`, routes served by `src/icon-png-bytes.ts`) into `.kody/`.
## PWA / SW
Register `${assetBasePath}/sw.js` with `{ scope: `${appBasePath}/` }`. Precache via `GET …/_assets/__version.json` → `clientModuleUrl`. Icons via starter `src/icon-png-bytes.ts` routes (192 + 512; TanStack koala default). Manifest `crossorigin="use-credentials"`.
## 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
```ts
import { kody } from 'kody:runtime'
export default async function main() {
const paths = ['/', '/notes', '/about', '/manifest.webmanifest', '/icons/icon-192.png', '/icons/icon-512.png', '/health']
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
}
```
## Prefer zero content layout shift (CLS)
Keep document flow stable. Update-available, toasts, and other post-hydration chrome use **fixed overlays** (see `UpdateBanner` / `.pak-update-toast`). Do not inject in-flow banners that push content down. Reserve height for async UI; hide install chrome in `display-mode: standalone`.
## Tooltips (`data-tip`)
Tip when the tip adds information: icon-only install in the header, truncated SHA, ISO behind local time. Labeled nav and action buttons rely on their visible words. Color swatches use visible text labels.
## Install header icon
Place `InstallCta` in the header brand row **to the right of the title**. Keep a reserved same-size slot (`pak-install-slot`) so BIP reveal does not shift layout. No large in-flow Install button.
## Optimistic UI + press feedback
Immediate `data-pending` / `aria-busy` on press. Use `app/ui/busy.ts` (`createBusyGate`) for spin-delay timing: delay busy labels ~200ms, keep them ≥400ms once shown, so fast ops do not flash "Loading…".
## Lucide icons
```bash
# From the kit repo while editing the starter twin, or copy script into the app:
node scripts/add-lucide-icon.mjs download trash-2 plus
```
Import generated React SVG components from `app/icons/{name}.tsx`. Icon-only controls get `data-tip` + `aria-label`; labeled buttons keep visible text.
## packageStorage migrations
```ts
import { packageStorage } from 'kody:runtime'
import {
createMigrationRunner,
} from 'kody:@kentcdodds/package-app-kit/storage-migrations'
const ensureSchema = createMigrationRunner({
storage: packageStorage(),
versionKey: '__PACKAGE_ID__-schema-version',
migrations: [/* { version, name, up } */],
})
await ensureSchema()
```
See `app/data/notes.ts` for a copy-paste example.