# Stryde Superapp SDK

The governed design surface every hosted app builds against. Link two
stylesheets, map your tokens to the platform names, build with the `.stryde-*`
classes — and your app reads **1:1 with the Stryde homepage**, in light and dark,
with a compliant side menu, automatically.

> **Get it:** download `stryde-superapp-sdk.zip` from the wiki **Downloads**
> section (or `GET /sdk/stryde-superapp-sdk.zip`). The kit is re-bundled on
> every download — the link is never stale.
>
> **Current release:** `stryde@2.2` — see [`CHANGELOG.md`](./CHANGELOG.md) for
> what's new (Notification Center, `app_key` attribution, S3 attachments,
> canonical top bar, app icon system, TypeScript types).

---

## What's in the kit

| File | What it is |
|------|------------|
| `stryde.css` | The **skin** — brand accents, the platform font, the ambient glow, and the `.stryde-*` components resolved from **hex** tokens (renders on the shell/wiki). |
| `stryde-tokens.css` | The canonical **surface palette** (warm-beige light / deep-purple dark) in **HSL-channel** form, **plus** the app-usable `.stryde-*` and `.stryde-side*` classes. This is what makes an app match. |
| `userbtn.js` | Drop-in Clerk user-avatar mounter for any nav (`#s-userbtn` / `#userbtn`). |
| `notifications.js` + `notifications.css` | The **Notification Center** — bell + glass drawer (inbox, detail, structured responses). Self-contained, themed, RTL-safe. Shell chrome; see below. |
| `notifications-page.js` | Drives the full-screen `/notifications` page; reuses `window.StrydeNotif` so the inbox card / detail rendering is one source of truth. |
| `types/index.d.ts` + `types/skin.d.ts` + `types/notifications.d.ts` | **TypeScript declarations** for the full `SdkContext` runtime, the skin helpers, and the `POST /v1/notifications` emit contract. Point your `tsconfig.json` paths at these (or `npm i -D @stryde/superapp-sdk`). |
| `starter/` | A copy-me Next.js scaffold already wired to the SDK (manifest, Tailwind token map, layout, page, side nav). |
| `manifest.example.json` | A gate-passing example manifest (`design_system` + `stylesheets`). |
| `app-standards.md` | The full app contract + the deploy-gate checklist. |
| `CHANGELOG.md` | Release history; bump `?v=` when adopting a new release. |
| `starter.html` | The live **plug-and-play proof** (also at `/sdk/starter.html`). |

These files are served live too: `https://stryde.evolvebyai.tech/sdk/<file>`.

---

## Quick start (3 steps)

### 1. Link both stylesheets — in this order, last in `<head>`

```html
<link rel="stylesheet" href="/sdk/stryde.css?v=3" />
<link rel="stylesheet" href="/sdk/stryde-tokens.css?v=3" />
```

> Bump the `?v=` query when you adopt a new SDK release (see `CHANGELOG.md`).
> The `design_system` id in your manifest (`"stryde@2"`) is unchanged.

`stryde-tokens.css` loads last on purpose: it is **unlayered**, so it overrides
your app's own `@layer base` tokens (it becomes the source of truth) *and* the
skin's hex `.stryde-*` (so the components work inside your app). In Next.js, put
the links in your root `layout.tsx` `<head>` (see `starter/frontend/app/layout.tsx`).

### 2. Map your Tailwind utilities to the SDK token NAMES (HSL channels)

This is the one hard requirement. Use the SDK's token names, read as
`hsl(var(--token) / <alpha-value>)`, so `bg-bg`, `text-ink`, `border-line/10`,
`text-brand-violet` resolve to the platform palette and re-theme automatically:

```ts
// tailwind.config.ts  (full version in starter/frontend/tailwind.config.ts)
export default {
  darkMode: 'class',
  theme: { extend: { colors: {
    bg:        'hsl(var(--bg) / <alpha-value>)',
    'bg-elev': 'hsl(var(--bg-elev) / <alpha-value>)',
    'bg-soft': 'hsl(var(--bg-soft) / <alpha-value>)',
    ink:       'hsl(var(--ink) / <alpha-value>)',
    'ink-2':   'hsl(var(--ink-2) / <alpha-value>)',
    'ink-3':   'hsl(var(--ink-3) / <alpha-value>)',
    line:      'hsl(var(--line) / <alpha-value>)',
    'brand-violet': 'hsl(var(--brand-violet) / <alpha-value>)',
    'brand-azure':  'hsl(var(--brand-azure) / <alpha-value>)',
    'brand-teal':   'hsl(var(--brand-teal) / <alpha-value>)',
  } } },
}
```

Ship a *fallback* copy of the token values in your `globals.css` under
`@layer base` (so the app still renders if the SDK sheet is unreachable). The
unlayered SDK sheet always wins. Copy `starter/frontend/app/globals.css`.

Theme switching is just `.dark` on `<html>`. Add a pre-paint script (in the
starter) to avoid a flash.

### 3. Build with the `.stryde-*` classes

```html
<span class="stryde-eyebrow">★ Section</span>
<h1 class="stryde-hero">Build it once. <span class="g">Look like Stryde.</span></h1>
<p class="stryde-lead">…</p>

<div class="stryde-card">…</div>
<button class="stryde-btn stryde-btn-primary">Primary →</button>
<button class="stryde-btn stryde-btn-ghost">Secondary</button>
<input class="stryde-input" placeholder="…" />
```

**Component catalog** (all re-theme automatically):

- Surfaces — `.stryde-card`, `.stryde-panel`
- Headers — `.stryde-hero` (+ `.stryde-grad` / `.g` for the violet→azure gradient word), `.stryde-eyebrow`, `.stryde-lead`, `.stryde-section`
- Buttons — `.stryde-btn` + `.stryde-btn-primary` (gradient) / `.stryde-btn-ghost`
- Controls & data — `.stryde-input`, `.stryde-pill`, `.stryde-table`, `.stryde-note`

---

## The side menu (the only app nav allowed)

The Stryde **shell owns the top nav** (logo, app switcher, language/theme, your
avatar). Apps must **not** build a top nav — use a side menu built from the SDK
classes (it matches the homepage glass aesthetic):

```html
<nav class="stryde-side">
  <a class="stryde-side-brand" href="/"><img src="/wiki/stryde-dark.png" alt=""/> <span>My App</span></a>
  <div class="stryde-side-section">Workspace</div>
  <a class="stryde-side-item active"><svg class="stryde-side-icon" viewBox="0 0 24 24"><!--…--></svg> Overview</a>
  <a class="stryde-side-item"><svg class="stryde-side-icon" viewBox="0 0 24 24"><!--…--></svg> Reports</a>
</nav>
```

A ready React component is in `starter/frontend/components/side-nav.tsx`.

---

## The user avatar (optional, for standalone pages)

Inside the shell you get the avatar for free (the shell's top nav). For a
standalone page that has its own nav, drop in:

```html
<div id="s-userbtn"></div>
<script src="/config.js"></script>     <!-- exposes window.SUPERAPP.clerkPk -->
<script src="/sdk/userbtn.js"></script>
```

---

## The Notification Center (shared by every app)

The platform has a built-in **Notification Center** — a bell + glass drawer in
the shell's top nav, on every surface. It's a platform capability, not a
per-app feature: your app **emits** notifications through the platform and the
shell renders them for the recipient (inbox, detail, and structured responses).

**You don't build any UI.** Inside the shell the bell appears automatically
(`/sdk/notifications.js` is shell chrome; it intentionally no-ops inside an app
iframe so there's no double bell). To put the bell on a *standalone* page that
has its own top bar, drop in `<div id="s-notif"></div>` + `/config.js` +
`/sdk/notifications.js` (same pattern as the avatar).

**Emit a notification** — one authenticated call (service key or user):

```bash
POST /v1/notifications
{
  "user_id": 42,                       // recipient (or "recipients": [1,2,3])
  "type": "myapp.invoice.approval",    // namespaced producer event
  "title": { "en": "Approve invoice #1029", "ar": "..." },
  "body":  { "en": "Vendor X submitted a $4,200 invoice." },
  "category": "approval",              // notification.category lookup
  "priority": "high",                  // low|normal|high|urgent|critical
  "interaction": "approve_reject",     // see below
  "action_required": true,
  "config": { "require_rejection_reason": true },
  "due_at": "2026-06-10T17:00:00Z",
  "link": "/myapp/invoices/1029"
}
```

**Interaction types** (`interaction` + matching `config`) render the response
controls for you: `acknowledge`, `approve_reject`, `yes_no`, `single_choice`,
`multiple_choice`, `custom_choice`, `text`, `file_upload`, `comment`,
`composite`, or `none` (informational). Choices live in
`config.choices: [{id,title,description,recommended,risk}]`; text takes
`config.max_length`; composite takes `config.components: [{type,required}]`.

Responses, comments and the full timeline are captured and auditable. The
governed fields (`category` / `priority` / `interaction` / status) are
platform **lookups** — see `GET /v1/notifications/meta` for the live option
lists. Full endpoint set: `GET /v1/notifications` (inbox, filters),
`/{id}` (detail), `POST /{id}/respond`, `/{id}/read`, `/read-all`,
`/{id}/comments`, `/{id}/assign`, `/{id}/escalate`, `/{id}/cancel`,
`GET|PUT /v1/notifications/preferences`.

---

## App icons (Higgsfield-rendered, served by the platform)

Every hosted app gets a consistent visual identity through the platform's icon
set — you don't ship images, you reference them:

| Path | Use it for |
|------|------------|
| `/admin/icons/256/<app_key>.png` | Dark-theme app tile (launcher chip, sidebar brand) |
| `/admin/icons/light/256/<app_key>.png` | Light-theme app tile (pair with the dark via `.dark-only` / `.light-only`) |
| `/admin/icons/heroes/<app_key>.png` | App detail hero / launcher hero |
| `/admin/icons/empty/<app_key>.png` | Empty-state art inside the app |

```html
<span class="ic">
  <img class="dark-only"  src="/admin/icons/256/myapp.png"       alt="" />
  <img class="light-only" src="/admin/icons/light/256/myapp.png" alt="" />
</span>
```

If you need a new icon for your app, drop a 256px PNG (dark + light) at those
paths and the launcher, sidebar, and detail surfaces pick them up automatically.

---

## TypeScript types

The kit ships `.d.ts` files in `types/` covering the **full** SDK surface so
TypeScript apps get inference and autocomplete with zero runtime dependency:

- **`types/index.d.ts`** — the mount context: `SdkContext`, `AuthApi`,
  `ApiClient`, `TelemetryApi`, `I18nApi`, `ThemeApi`, `LookupsApi`, `BusApi`,
  `NavApi`, `ErrorsApi`, `AppMount`, `ApiError`.
- **`types/skin.d.ts`** — `applyStrydeSkin`, `bindIframeSkin`, `SKIN_ID`,
  `SKIN_VERSION`, `SKIN_HREF`.
- **`types/notifications.d.ts`** — `EmitNotificationRequest`,
  `NotificationInteraction`, `NotificationConfig`, `NotificationChoice`,
  `NotificationComponent`, `StrydeNotifRuntime` (the
  `window.StrydeNotif` surface for standalone pages reusing the renderer).

```ts
// app/types.d.ts (or wire via tsconfig "paths")
import type { SdkContext, AppMount } from './stryde-superapp-sdk/types';
import type { EmitNotificationRequest } from './stryde-superapp-sdk/types/notifications';
```

---

## Ship it: the manifest contract

Your `manifest.json` must declare the current design system **and** link both
sheets, or the deploy gate (`GATE.UI.DESIGN_SYSTEM`) rejects it:

```json
{
  "frontend": {
    "route": "/my-app",
    "mount": "default",
    "design_system": "stryde@2",
    "stylesheets": ["/sdk/stryde.css?v=3", "/sdk/stryde-tokens.css?v=3"]
  }
}
```

See `manifest.example.json` for a complete one, and `app-standards.md` §6.5 /
§16 for the full rules.

---

## Verify your app matches

Open `/sdk/starter.html` — it links **only** the SDK and renders the entire
catalog in both themes. If your screen matches it (and the homepage), you're
compliant. Quick checklist:

- [ ] Both `/sdk/*.css` linked, `stryde-tokens.css` last.
- [ ] Tailwind colours mapped to the SDK token names (no hardcoded hex).
- [ ] Surfaces/buttons/inputs use `.stryde-*`.
- [ ] Navigation is a `.stryde-side*` side menu — no top nav.
- [ ] `manifest.json` → `design_system: "stryde@2"` + both `stylesheets`.

---

*Design language version: `stryde@2`. Bump the `?v=` query + `design_system` id
together when the platform skin changes.*
