# Discipline Upgrade — Discovery Report
_Generated: 2026-05-26_

## 1. Existing Discipline System (End-to-End Map)

### Storage
- **Database**: `~/.infinibot/discipline.db` (SQLite, WAL mode)
- **Tables** (7):
  - `discipline_blocks` — scheduled time-boxes (startMinute/endMinute as minutes-from-midnight)
  - `discipline_routine_completions` — per-item per-day checklist completions
  - `discipline_logs` — actual activity log entries (planned/probe/manual/abort)
  - `discipline_settings` — global settings (4 columns: militant, probeIntervalMin, dayStartHour, dayEndHour)
  - `discipline_block_completions` — block-level done marks with saved-minutes auto-advance
  - `discipline_block_overrides` — per-block start-time overrides (set when an earlier block finishes early)
  - `discipline_templates` — named schedule snapshots for apply/save

### Critical Gap: Settings Persistence
The server-side `discipline_settings` table only stores **4 fields** (`militant`, `probeIntervalMin`, `dayStartHour`, `dayEndHour`).  
The UI controller (`ui/src/ui/controllers/discipline.ts`) has a much richer `DisciplineSettings` type including `telegramEnabled`, `telegramChatId`, `telegramEvents`, `quietHoursStart/End`, `ai`, etc.  
**Result**: telegram config and quiet hours are lost on every page reload because the extended fields are never persisted.  
**Fix**: Extend `discipline_settings` table with new columns (additive ALTER TABLE), extend store + gateway handler.

### Store Class
- `src/discipline/store.ts` → singleton `getDisciplineStore(stateDir)`
- Same instance shared between gateway RPC handlers and the MCP tool → agent writes immediately visible to UI

### Gateway RPC Handlers
- `src/gateway/server-methods/discipline.ts`
- `discipline.state` returns blocks, logs, settings, routineCompletions, blockCompletions, blockOverrides
- `discipline.settings.update` — currently only saves the 4 basic fields, silently discards extended fields

### MCP Tool
- `src/agents/tools/discipline-tool.ts`
- Tool name: `infinibot_discipline`
- 22 actions covering full CRUD for blocks, logs, settings, scoring, templates

### UI View
- `ui/src/ui/views/discipline.ts` — ~1000 lines, sub-views: timeline / blocks / score / settings
- Uses functional-renderer pattern (not custom elements)

### UI Controller
- `ui/src/ui/controllers/discipline.ts` — DisciplineState machine
- `startDisciplineTicker()` — 20s polling interval, calls `tickAutoNotifications()`
- `tickAutoNotifications()` in `discipline-notify.ts` handles block start/end transition detection

---

## 2. Bubble Clock / Center Navbar

- **Component**: `ui/src/ui/components/terminal-clock.ts`
- **Mounting**: `app-render.ts` line ~1149-1158 inside `<div class="topbar-center">`
- **Pattern**: Functional renderer `renderTerminalClock(props, isAlarmSounding)` — NOT a custom element
- **No existing notification surface** below it — alerts need to be a new absolute/fixed element positioned below `topbar-center`
- Clock has localStorage state (`infinibot-terminal-clock`) for alarms/timers

---

## 3. Telegram Integration

### Server-Side Infrastructure (fully working)
- `src/telegram/send.ts` → `sendMessageTelegram(to, text, opts)` — standalone server-side send function
  - Loads config from `loadConfig()` automatically
  - Token resolves from: (1) `opts.token`, (2) `botToken` in `infinibot.json channels.telegram[0]`, (3) env `TELEGRAM_BOT_TOKEN`
  - `to` is the chat ID string (e.g. `"-1001234567890"` or `"@username"`)

### Default Config Path
- `~/.infinibot/infinibot.json` → `channels.telegram[0].botToken`
- Env override: `TELEGRAM_BOT_TOKEN`

### UI-Side Send (already implemented)
- `ui/src/ui/controllers/discipline-notify.ts`
  - `sendTelegram(client, state, message)` — routes via gateway `send` RPC → `{ channel: "telegram", to: chatId, message, idempotencyKey }`
  - `formatBlockMessage(state, block, kind, extra)` — human-readable block message builder
  - `shouldSendTelegram(state, event, opts)` — checks enabled flag + quiet hours
  - `tickAutoNotifications(state, client)` — called every 20s from discipline ticker, detects block transitions, fires telegram + browser notifications
  - **Already handles**: blockStart, blockEnd, blockComplete, abort, slip

### Reuse Plan
- UI notifications: extend `tickAutoNotifications()` to add 5-min warnings + fire `discipline:alert` window events
- Server-side bridge: use `sendMessageTelegram()` directly in `src/discipline/telegram-bridge.ts`
- Chat ID: read from `getDisciplineStore().getSettings().telegramChatId` (once extended settings are persisted)

---

## 4. Event Bus

- **Module**: `src/infra/agent-events.ts`
- **API**:
  - `emitAgentEvent(evt)` — broadcast to all listeners
  - `onAgentEvent(listener)` — subscribe, returns unsubscribe fn
- **Streams**: `"lifecycle"` | `"tool"` | `"assistant"` | `"error"`
- **Data shape**: `{ runId, seq, stream, ts, data, sessionKey }`
- Available server-side only (no UI access)
- Will be the input for auto-log attribution

---

## 5. Existing Alert / Toast Infrastructure

| System | File | Trigger | Notes |
|--------|------|---------|-------|
| Toast | `ui/src/ui/views/toast-notifications.ts` | window `show-toast` event | Colored pills, auto-dismiss, 4 types |
| Live Action Toast | `ui/src/ui/components/live-action-toast.ts` | window `ui-control:action-*` events | Agent action feedback, fixed top-left |
| Browser Notification | `discipline-notify.ts` | Militant mode only | OS-level, requires permission |

**No existing clock-anchored alert** — new `discipline-alert.ts` fills this gap.

---

## 6. Decided Architecture (Phase-by-Phase)

### Phase 0 ✅ (this document)

### Phase 1: Auto-log Engine
- New table `discipline_events(id, blockId, kind, payload, createdAt, attributedAt)` added to `discipline.db`
- `src/discipline/auto-log/index.ts` exports `startDisciplineAutoLog(stateDir)` — subscribes to `onAgentEvent`, attributes to current block by local-time comparison
- New gateway handler `discipline.events.list` exposes today's events to UI
- Extended `discipline_settings` table stores full settings (telegram, quiet hours, AI) via additive ALTER TABLE

### Phase 2: Start-my-Day
- New MCP tool actions: `start_day`, `end_day`
- `start_day`: finds nearest scheduled block (within ±60 min), shifts its start to now; else creates default "Work" block
- `end_day`: closes active block, returns today's summary
- Gateway handlers: `discipline.start_day`, `discipline.end_day`

### Phase 3: Alert Component
- `ui/src/ui/components/discipline-alert.ts` — functional renderer, dispatched via `discipline:alert` window events
- Fixed position: `top: 56px; left: 50%; transform: translateX(-50%); z-index: 1001`
- CSS slide-down + glow animation (transform/opacity only, respects prefers-reduced-motion)
- Alert kinds: `block-warning-5`, `block-start`, `block-end`, `block-missed`, `day-summary`
- `tickAutoNotifications` extended to fire window events + 5-min warnings

### Phase 4: Telegram Bridge
- `src/discipline/telegram-bridge.ts` — 60s server-side timer, reads settings from store, sends via `sendMessageTelegram`
- Started as module-level side effect in `server-methods/discipline.ts`
- UI indicator: small dot near the clock (green/gray/red) + status popover with "last sent" + test button

### Phase 5: Discipline Page Upgrade
- Timeline strip below today's blocks — auto-log event dots per block (kind icons, hover details)
- Per-block event counters: "N events" badge loaded from `discipline.events.list`

---

## 7. Files I Own (Writing Freely)

| File | Action |
|------|--------|
| `src/discipline/types.ts` | Extend (DisciplineEvent + full DisciplineSettings) |
| `src/discipline/store.ts` | Extend (discipline_events table + methods, full settings) |
| `src/gateway/server-methods/discipline.ts` | Extend (events handler, extended settings, start_day/end_day) |
| `src/discipline/auto-log/index.ts` | **NEW** |
| `src/discipline/telegram-bridge.ts` | **NEW** |
| `src/agents/tools/discipline-tool.ts` | Extend (start_day, end_day actions) |
| `ui/src/ui/components/discipline-alert.ts` | **NEW** |
| `ui/src/ui/controllers/discipline-notify.ts` | Extend (5-min warnings, window events) |
| `ui/src/ui/views/discipline.ts` | Extend (event timeline, counters, telegram badge) |
| `ui/src/ui/app-render.ts` | Additive (mount discipline-alert, telegram indicator) |

## 8. Telegram Config Used

The server-side bridge reads `chatId` from `getDisciplineStore().getSettings().telegramChatId` (persisted in DB after the settings table extension). Bot token comes from the existing `loadConfig()` / `sendMessageTelegram()` infrastructure — no credentials are hardcoded or re-asked.
