61 lines
2.6 KiB
Markdown
61 lines
2.6 KiB
Markdown
# Copilot Instructions
|
|
|
|
## Architecture Overview
|
|
|
|
Three-component system for remote-controlling a TV display from a mobile phone:
|
|
|
|
- **`mobile/`** — Expo React Native app (the "remote control"). Sends commands to the backend.
|
|
- **`tv/`** — Vite + React web app (the TV display). Runs in a browser, polls backend every 5s when fullscreen.
|
|
- **`functions/control/`** — Python serverless function deployed on `shsf-api.reversed.dev`. Acts as state store; persists to `/app/state.json` on the container.
|
|
|
|
## API / Backend
|
|
|
|
All communication goes through a single endpoint:
|
|
```
|
|
https://shsf-api.reversed.dev/api/exec/15/1c44d8b5-4065-4a54-b259-748561021329/{route}
|
|
```
|
|
|
|
Routes (all `POST` except pull routes which are `GET` implicitly via `fetch`):
|
|
- `push_text` — body: `{ title: string }`
|
|
- `push_dismiss_text`
|
|
- `push_image` — body: `{ image_b64: string, caption: string }`; uploads to MinIO, stores public URL
|
|
- `push_dismiss_image`
|
|
- `pull_full` — returns full state `{ text, image_popup }`
|
|
|
|
Images are stored in MinIO at `content2.reversed.dev`, bucket `tv-control`.
|
|
|
|
## Adding a New Content Type
|
|
|
|
1. Add state shape to `DEFAULT_STATE` in `functions/control/main.py`
|
|
2. Add `push_*` / `pull_*` handlers in `main(args)` in the same file
|
|
3. Add a TypeScript interface and `useState` in `tv/src/App.tsx`; render the popup in the fullscreen branch
|
|
4. Add a page in `mobile/src/pages/`
|
|
5. Register it in the `TABS` array in `mobile/App.tsx` with `hideInNav: true` (prevents it appearing in `BottomNav`, navigation is triggered programmatically via `navigate()`)
|
|
|
|
## Mobile App Conventions
|
|
|
|
- Uses a **custom in-memory router** (`mobile/src/router.tsx`) — no expo-router or react-navigation. `Route` type is a string union: `"home" | "text" | "image"`.
|
|
- `TABS` in `mobile/App.tsx` is the single source of truth for routes and pages. `BottomNav` imports `TABS` directly from `App.tsx`.
|
|
- Tabs with `hideInNav: true` are reachable only via `navigate(route)`, not from the bottom bar.
|
|
- Styling uses React Native `StyleSheet` throughout — no CSS or Tailwind.
|
|
|
|
## TV App Conventions
|
|
|
|
- Uses **Tailwind CSS v4** via `@tailwindcss/vite` (no `tailwind.config.js` — config lives in `vite.config.ts`).
|
|
- Polling only starts when the browser enters fullscreen (`screenStatus === "fullscreen"`).
|
|
- State shape mirrors `DEFAULT_STATE` from the Python function exactly.
|
|
|
|
## Dev Workflows
|
|
|
|
```bash
|
|
# TV display
|
|
cd tv && pnpm dev
|
|
|
|
# Mobile app
|
|
cd mobile && pnpm start # Expo Go / dev server
|
|
cd mobile && pnpm android # Android emulator
|
|
cd mobile && pnpm ios # iOS simulator
|
|
```
|
|
|
|
Package manager: **pnpm** for both `tv/` and `mobile/`. Python function has no local runner — deploy changes directly.
|