# Smart Home Tablet — Handoff

Документ для следующей сессии. Где что лежит, как деплоить, что сделано.

---

## Что это

Дашборд для планшета на стене: `https://tablet.digital-home.site`.
Next.js 14 App Router, TypeScript, standalone Docker build.

Контейнер живёт на сервере `cosmo@192.168.31.60`, образ собирается self-hosted Gitea Actions runner'ом и пушится через `docker stop/rm/run` (без compose для этого сервиса).

---

## Где файлы

### Локально (рабочая копия)
```
/tmp/tablet-work/smart-home-tablet/
```
Это git-clone из `https://git.digital-home.site/daniil/smart-home-tablet`. **Ремоут уже настроен с токеном в URL** — `git push origin main` работает без пароля.

### На сервере (192.168.31.60)
```
/opt/digital-home/smart-home-tablet/         # код (что задеплоено)
/opt/digital-home/smart-home-tablet-data/    # JSON-стораджи (volume → /data в контейнере)
  ├─ tablet-notes.json
  ├─ tablet-timers.json
  └─ tablet-countdowns.json
/opt/digital-home/tablet.env                 # секреты (HA_TOKEN, ELEVENLABS_*, VOICE_API_KEY, и т.д.)
```

### Workflow деплоя
```
/opt/digital-home/smart-home-tablet/.gitea/workflows/deploy.yml
```
Триггер: push в `main`. Делает `git pull → docker build → stop/rm/run`. Контейнер: `tablet-yfh53kixpwkjlo4zibglx4n2` на порту 3006, traefik роутит `tablet.digital-home.site` → 3000 внутри.

---

## Как деплоить

```bash
cd /tmp/tablet-work/smart-home-tablet
# правки...
NODE_OPTIONS="--max-old-space-size=4096" npm run build   # tsc на дефолтном heap падает OOM
git add -A && git commit -m "..." && git push origin main
```

Дальше Gitea Actions сам пересоберёт. Как проверить, что задеплоилось:
```bash
ssh -i ~/.ssh/id_ed25519 cosmo@192.168.31.60 \
  "docker ps --filter name=tablet --format '{{.CreatedAt}}'"
curl -sk -o /dev/null -w "%{http_code} %{time_total}s\n" https://tablet.digital-home.site/
```

Свежий `CreatedAt` = новый билд поднялся.

---

## SSH

```bash
ssh -i ~/.ssh/id_ed25519 cosmo@192.168.31.60
```
Ключ `~/.ssh/id_ed25519` авторизован. **Не** `daniil@192.168.31.103` — туда с этой машины ключ не положен.

---

## Голосовой ассистент

Tablet — **только UI**. Слой, отвечающий за wake-word/STT/LLM, живёт **снаружи** (на 192.168.31.103, плюс ElevenLabs через прокси `192.168.31.103:8888`).

Поток событий:
```
Внешний Python-агент ──POST──▶ /api/voice/event   (auth: VOICE_API_KEY)
                                      │
                                      ▼
                                 voiceBus (in-memory EventEmitter)
                                      │
                                      ▼
            Браузер ◀──SSE──── /api/voice/stream
                                      │
                                      ▼
                              <VoiceOverlay />
```

### Файлы голосового стека (в этом репо)
```
app/api/voice/
  ├─ event/route.ts      # POST: принимает {event, agent, text} от Python-агента
  ├─ stream/route.ts     # GET (SSE): отдаёт события в браузер
  ├─ tts/route.ts        # POST: проксирует в ElevenLabs (cosmo / lusya голоса)
  ├─ timer/route.ts      # голос-операции с таймерами
  └─ tools/              # tool-calls от LLM (открыть таб, поставить таймер, ...)

lib/voice-bus.ts         # EventEmitter, шарится между event и stream
lib/voice-tools.ts       # описание tools для LLM

components/VoiceOverlay.tsx   # Siri-style орб, рендерит wake/listening/command/response/error
```

Состояния (см. `VoiceOverlay.tsx`): `idle | wake | listening | command | response | error`. Два агента (`cosmo` / `lusya`) — каждому своя цветовая пара (фиолетовая / розовая) и свой ElevenLabs voice ID. Голоса в `tablet.env`: `COSMO_TTS_VOICE`, `LUSYA_TTS_VOICE`.

Агент сам шлёт `idle` когда закончил; в overlay есть safety-таймер 60с на случай падения агента.

**Где сам Python-агент** — на `192.168.31.103` (рабочая машина пользователя). Этот репо его не содержит и не деплоит.

---

## Структура UI

```
app/
  layout.tsx, page.tsx          # Home: погода + комнаты + транспорт + ноуты + календарь
  globals.css                   # дизайн-токены: --surface-*, --data-*, --space-*, утилиты .num/.grain/...
  api/
    auth/        ha/         weather/    transport/
    notes/       calendar/   tasks/      savings/
    countdowns/  voice/      

components/
  TopBar.tsx                    # часы, погода-чип, HA-статус (44px hit-zone), сенсор-чип
  RoomTabs.tsx                  # переключатель Home/Зал/Кухня/Спальня
  DeviceCard.tsx                # лампы/тв/кондей/пуриф — accent по типу из DEVICE_ACCENT
  TransportWidget.tsx           # трамваи, цвета через --data-good/info/danger
  WeatherAnimation.tsx          # SVG-анимация по condition
  NotesTab.tsx                  # стикеры (drag-to-trash, --data-* цвета)
  CalendarTab.tsx               # 3 модалки, у всех data-swipe-ignore
  TimerHomeWidget.tsx, TimerModal.tsx, TimerWidget.tsx
  Sidebar.tsx
  VoiceOverlay.tsx              # см. секцию выше
  FocusCard.tsx                 # ⚠ написан, но НЕ используется на Home (см. ниже)
  CountdownCard.tsx             # ⚠ написан, но НЕ используется на Home
```

### Свайп между табами
В `app/page.tsx` — handler на `<main>` через `onPointerDown/Up`, ref `swipeStart`. Условия срабатывания: `|dx|>|dy|*1.6 && |dx|>90 && (dt<600 || |dx|>160)`. Bail-out если `e.target.closest('[data-swipe-ignore]')` — этот атрибут стоит на всех fixed-overlay (TimerModal, VoiceOverlay, модалки CalendarTab, weather day modal, draggable note).

---

## Что было сделано в прошлой сессии

1. **CSS-рефакторинг** — `app/globals.css`: добавлены семантические токены `--data-cool/info/good/warm/hot/danger/rose/violet/mood` + их `-bg` варианты через `color-mix(in srgb, ...)` для тёмной и `.light` темы. `--space-1..6`, `--touch-min: 44px`, `--touch-comfy: 56px`. Утилиты: `.num`, `.num-display` (tabular-nums slashed-zero), `.grain` (SVG-noise через `::after`), `.hit-zone`, `.eyebrow`, `.focus-card`, `.divider`.
2. **Tap-targets подняты до 44px** — TopBar (HA-dot обёрнут в hit-zone), NotesTab (плюс/удалить), CalendarTab (close/nav/list), TimerHomeWidget.
3. **Иконки/цвета** через токены вместо хардкода — TransportWidget (route.color = var(--data-*)), DeviceCard (DEVICE_ACCENT по типу), TopBar-сенсоры.
4. **Свайп между табами** — добавлен в `app/page.tsx`, c bail-out по `[data-swipe-ignore]`.
5. **Forecast day buttons / WeatherDayModal** — увеличены тапы и шрифты.
6. **API `/api/countdowns`** — GET/POST/PUT/DELETE, бэкенд `/data/tablet-countdowns.json`. Дефолтная запись: Токио 2026-10-15.
7. **FocusCard.tsx + CountdownCard.tsx** написаны, но **с Home убраны** по фидбеку: пользователю не понравилось, что виджет погоды стал меньше трамваев и пропали `feelsLike` / `humidity` / `WeatherAnimation`. Сейчас на Home — старый weather-hero с display-цифрой 76px, фоновой `WeatherAnimation size=180` (opacity 0.14), inline `WeatherAnimation size=60`, и тремя `.eyebrow + .num` блоками снизу (Ощущается/Влажность/Ветер).

### Что НЕ делать без явной просьбы
- **Не возвращать FocusCard на Home** и **не показывать CountdownCard на Home** — пользователь явно отверг.
- **Не делать виджеты Habits и Pulse** — пользователь сказал "пока что их не добавляем".

### Что осталось как orphan-файлы (есть, но не подключено)
- `components/FocusCard.tsx` — context-engine с приоритетами bill-due / event-upcoming / tram-imminent / morning-outfit / countdown / night / quiet. Можно использовать в будущем, но Home не место.
- `components/CountdownCard.tsx` — ротация записей из `/api/countdowns` каждые 8с.

---

## Известные грабли

- **`tsc --noEmit` ловит OOM** на дефолтном heap — всегда `NODE_OPTIONS="--max-old-space-size=4096"`.
- **`lucide-react`**: нет экспорта `Tram`, есть `TramFront`. Грепать `node_modules/lucide-react/dist/lucide-react.d.ts` если сомневаешься.
- **Git push с локальной машины**: ремоут уже c токеном, не сбрасывать (`git remote -v` покажет `https://<token>@git.digital-home.site/...`). Если потерялся — взять с сервера: `ssh cosmo@192.168.31.60 'cat /opt/digital-home/smart-home-tablet/.git/config'`.
- **Volume `/data`** в контейнере — это `/opt/digital-home/smart-home-tablet-data/` на хосте. JSON-сторадж (notes/timers/countdowns) переживает редеплой только потому, что лежит там.

---

## Последние коммиты сессии
- `e328055` — большой CSS / tap-target / swipe / FocusCard / CountdownCard / countdowns API
- `a97dd11` — revert Home: вернул weather-hero с анимацией и деталями, убрал CountdownCard с главной

Оба задеплоены, прод 200 OK.
