Files
tv-control/.github/copilot-instructions.md

2.6 KiB

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

# 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.