# Changelog ## v1.33.0 - 2026-09-29 **Added** - `BRG_TELEGRAM_API_URL` env var / `telegram.api_url` config for routing Telegram traffic through a custom Bot API server or relay (closes #43). - `/searchmode [on|off]` bot command: per-profile allowlist-only mode for younger children. Hides the search bar and rejects `/search` and `/request` server-side, so the child can only browse allowlisted channels and already-approved videos (closes #45). - `TelegramConfig.api_url` is validated at load: it must be an `http(s)` URL with a host, and plain `http` logs a warning because the bot token travels in the request path. The startup log prints only the API host so credentials embedded in the URL are never logged. **Fixed** - `/changelog` no longer shows an unreleased section under the current version number. **Notes** - v1.32.0 (`BRG_EMBED_HOST`, see below) was never tagged or published as a release. Installs from published images (latest: v1.31.2) get those changes for the first time in this release. ## v1.32.0 - 2026-08-11 **Added** - `BRG_EMBED_HOST` env var for switching YouTube iframe origin without code changes or redeploy. Whitelist: `https://www.youtube.com`, `https://www.youtube-nocookie.com`. Defaults to nocookie. **Fixed** - YouTube embed origin switched back to `youtube-nocookie.com` — reduces DNS allowlist requirements (tablet no longer needs full YouTube site whitelisting). Tradeoff: nocookie embeds lack signed-in session, so "confirm you're not a bot" may reappear; set `BRG_EMBED_HOST=https://www.youtube.com` and restart if needed. - Service worker navigation timeouts: requests now timeout after 8s instead of hanging indefinitely on unreachable origins. Timeout returns a 503 error page ("Can't reach BrainRotGuard") with retry link, avoiding frozen-state on cached splash screen. - Service worker cache version bumped v2 → v3 to purge old caches on upgrade. - CSP `frame-src` updated to allow both `https://www.youtube.com` and `https://www.youtube-nocookie.com`. **Notes** - Real-device playback verification on nocookie origin pending; adjust `BRG_EMBED_HOST` if bot-check appears frequently. - `docs/setup.md` DNS section rewritten. It previously told self-hosters DNS blocking "no longer works" — true while youtube.com embeds were the default, wrong once nocookie is. Now documents both origins and states plainly that DNS cannot distinguish an embedded player from a browser tab on the same domain, which is the whole reason the nocookie default matters for enforcement. ## v1.31.2 - 2026-08-11 **Fixed** - **Security: re-enabled the player iframe `sandbox` attribute** — it was commented out in `c9b88a0` to work around what looked like broken playback on the test tablet, but the real cause was a DNS outage (already identified in `1511aef`) and the attribute was never restored. Every release from v1.30.x onward shipped without it, meaning YouTube's in-player links (video title, channel watermark, "Watch on YouTube") could navigate the child out of the app into a full browser YouTube session (fixes #42 — thanks @bquinn916) - Added `playsinline=1` to the embed URL — without it iOS hands playback to Apple's native fullscreen player, which renders outside the app's DOM and bypasses the pause overlay, end-of-video overlay, and custom fullscreen that normally cover YouTube's chrome. This is why the escape was more severe on iPad than Android - `test_service_worker_available_without_login` asserted a hardcoded cache name (`brainrotguard-static-v1`) and had been failing since the worker moved to v2; now matches any version so future cache bumps don't break the suite **Notes** - The sandbox retains `allow-same-origin`, which is required for the signed-in YouTube session that avoids the "confirm you're not a bot" wall (see #38) and for `enablejsapi` postMessage. It withholds `allow-popups` and `allow-top-navigation` — that omission is what blocks the escape - Both player fixes are unverified on iOS hardware; they are reasoned from documented iOS behaviour. Parents on iPad should enable **Guided Access** (Settings > Accessibility > Guided Access) as an OS-level lock, which does not depend on this fix being correct - Removed an unused `embed_url` template variable from the watch route — the template builds its own iframe `src` in JS, so the dead variable was a trap for anyone editing the wrong line ## v1.31.1 - 2026-04-09 **Fixed** - Switch embeds from `youtube-nocookie.com` to `youtube.com` — YouTube's bot detection blocks anonymous nocookie embeds with a "Sign in to confirm you're not a bot" wall; standard embeds share the tablet's Google session (closes #38) - Updated CSP `frame-src` header to allow `youtube.com` - Updated docs: README, setup guide, troubleshooting, design decisions ## v1.31.0 - 2026-03-25 **Added** - Watch history page: `/history` accessible from avatar dropdown, grouped by day with progress bars and infinite scroll (PR #33 — thanks @NoLooseEnds) - Activity row: homepage "Your Requests" reworked to "Activity" showing 6 most recent unfinished videos with progress bars, dismiss controls, and collapse/expand toggles (PR #34 — thanks @NoLooseEnds) - Progress bars on channel video catalog cards showing playback progress (PR #34) - Section collapse/expand toggles for all homepage sections with localStorage persistence (PR #34) - Requests page: `/requests` for one-off video approvals (pending + approved outside allowed channels) (PR #35 — thanks @NoLooseEnds) - Channel pills alphabetized on homepage (PR #34) - Rate limit handler returns JSON for `/api/` routes instead of HTML (PR #33) **Fixed** - `position_seconds` heartbeat field bounded to [0, 86400] — prevents arbitrary integers in database - `dismissed` catalog param validated against video ID regex for consistency with security model - `_annotate_progress` replaced full approved-videos table scan with targeted batch query — single JOIN instead of two separate queries - `get_active_videos` SQL includes `resume_seconds` in completion check — prevents phantom gap where SQL LIMIT applied before Python filter could return fewer results than available - `HAVING` clause includes `OR v.duration IS NULL` — partially-watched videos without stored duration no longer silently dropped - History infinite scroll retries on all fetch errors (not just HTTP 429) ## v1.30.0 - 2026-03-17 **Added** - i18n/localization system with English and Norwegian translations (PR #26 -- thanks @NoLooseEnds) - PWA support: installable as tablet app with manifest, service worker, standalone mode, safe-area insets (PR #29 -- thanks @NoLooseEnds) - Watch autoplay/resume: remembers playback position via localStorage, more reliable auto-start (PR #30 -- thanks @NoLooseEnds) - Persistent playback progress: watch progress stored to database and restored on return, survives app restarts (PR #32 -- thanks @NoLooseEnds) - Localized app name: "BrainRotGuard" renders as "HjerneVakt" in Norwegian (PR #31 -- thanks @NoLooseEnds) - `/autoload [on|off]` toggle: switch homepage between Show More buttons (default) and infinite scroll mode (PR #27 -- thanks @NoLooseEnds) - New `/api/catalog/status` endpoint for lightweight cache polling (PR #27) - Re-request flow: re-requesting a pending video resends the Telegram notification to parent (PR #27) - IntersectionObserver-based infinite scroll replaces "Show More" button on search results (PR #27) - Circular profile avatars with username in header (PR #28) - CSS fullscreen fallback for mobile/PWA standalone mode where native Fullscreen API is unavailable (PR #29) - Back navigation tracking via sessionStorage for smarter back links (PR #30) **Changed** - Simplified web header, navigation, and time budget UI (PR #28 -- thanks @NoLooseEnds) - Channel filter fix: `build_requests_row` now correctly filters allowlisted channels even when video has a channel_id but channel was added by name only (PR #27) **Fixed** - Norwegian revoke confirmation toast: "Fjernet!" (past tense) instead of "Fjern!" (imperative) or "Avslått!" (which duplicated "Denied") - Playback position cleared on time expiry instead of saved (prevents resuming from expired position) - Heartbeat dedup guard: lowered `_HEARTBEAT_MIN_INTERVAL` from 10s to 8s to maintain safety margin (must be < client heartbeat interval; PR #32 reduced client interval to 10s) ## v1.28.0 - 2026-03-01 **Changed** - Shorts now shuffle daily — different selection each day instead of the same popularity-ordered shorts repeating indefinitely ## v1.27.7 - 2026-03-01 **Fixed** - Video requests from new/unknown channels failed with internal server error since v1.27.5 — wrong keyword argument (`view_count` instead of `yt_view_count`) in the pending-approval code path ## v1.27.6 - 2026-02-26 **Changed** - Removed standalone `/help` web page — command reference consolidated into [docs/telegram-commands.md](docs/telegram-commands.md) - Reorganized telegram-commands.md: `/setup` wizard promoted to top as recommended entry point, added contextual notes throughout ## v1.27.5 - 2026-02-26 **Added** - YouTube view count (`yt_view_count`) stored at request time — "Your Requests" section now shows real YouTube views instead of internal watch count **Changed** - Separated YouTube views (`yt_view_count`) from internal watch counter (`view_count`) in database — parent-facing bot commands (`/approved`, `/stats`) show internal counts, web UI shows YouTube views ## v1.27.4 - 2026-02-26 **Added** - YouTube link sandbox: iframe `sandbox` attribute blocks all navigation to youtube.com (logo, title, channel, "Watch on YouTube" links) - Pause overlay: full-coverage overlay with play button when video is paused, hiding YouTube's recommended video strip - Early end-of-video overlay: covers the player with 15 seconds remaining to hide YouTube's recommended videos grid - Replay icon on end overlay (circular arrow button matching pause overlay style) - Custom fullscreen: our fullscreen wraps the video + overlays together, so pause/end overlays work in fullscreen too (YouTube's native fullscreen is disabled by sandbox) **Changed** - Removed `autofocus` from homepage search bar to prevent tablet keyboard from popping up on load ## v1.27.3 - 2026-02-25 **Changed** - `/time setup` wizard: context-aware navigation — back goes to setup hub when launched from onboarding, shows Done button when standalone ## v1.27.2 - 2026-02-25 **Added** - Back buttons on every step of the `/time setup` wizard for easy navigation ## v1.27.1 - 2026-02-25 **Changed** - Child name context now shown in all command headers (`/pending`, `/approved`, `/stats`, `/watch`, `/logs`, `/search`, `/time`, `/shorts`) - Multi-child context labels added to setup hub sub-menus (Time, Channels, Shorts) to show which child is being configured - Starter channels setup now displays child name context for clarity in multi-child environments ## v1.27.0 - 2026-02-25 **Added** - Interactive setup hub: `/start` and `/setup` now show a persistent message with 4 configurable sections (Children, Time Limits, Channels, Shorts) - Each section has its own sub-menu with inline buttons — tap to configure, tap Back to return to the hub - Children section: rename default profile, add children, set PINs — all via inline buttons and text replies - Channels section: browse and import starter channels per profile with Back to Setup navigation - Time Limits section: chains to existing `/time setup` wizard, returns to hub on completion - Shorts section: per-profile enable/disable toggle - Hub shows live status for all sections (child names, limit values, channel counts, shorts state) - First-run auto-message now sends the setup hub instead of a static welcome ## v1.26.6 - 2026-02-25 **Changed** - Emoji icons on all Telegram inline buttons (approve, deny, allow/block channel, revoke, category toggle) - Default daily time limit changed from 120 to 0 (unlimited) **Fixed** - Mobile search dropdown CSS cascade bug causing horizontal viewport overflow ## v1.26.5 - 2026-02-25 **Fixed** - Mobile search input no longer triggers iOS Safari auto-zoom (font-size 14px → 16px) ## v1.26.4 - 2026-02-25 **Fixed** - Container volume permissions on Unraid/NAS (entrypoint chown + gosu for mounted `/app/db`) **Added** - Unraid install instructions in README and setup guide (CA search + manual template download) - `TemplateURL` in Unraid template for Community Apps discovery ## v1.26.3 - 2026-02-25 **Changed** - Redesigned logo and favicon (new shield+brain icon, PNG favicons replace SVG) - Search bar hidden on login screen - Dockerfile supports env-only configuration (no config.yaml required) **Added** - Unraid Community Apps template (`unraid-template.xml`) - 512x512 square icon for Unraid/app stores - Updated demo video and screenshots ## v1.26.2 - 2026-02-25 **Fixed** - Replaced false-positive protocol mock test with structural method check - Strengthened wrong-PIN and invalid-video-ID integration tests (real assertions instead of conditionals) - Backfill task now properly cancelled on shutdown (faster stop) **Removed** - Dead `sample_config` fixture, unused config imports, dead `templates` import ## v1.26.1 - 2026-02-25 **Fixed** - Mobile search dropdown caused horizontal overflow/resize when opened (dropdown used absolute positioning with fixed 280px width; now uses viewport-relative fixed positioning on mobile) ## v1.26.0 - 2026-02-25 **Modularity refactor** — decomposed two monolithic files into 20 focused modules with dependency injection, declarative callback routing, and 241 automated tests. **Changed** - `web/app.py` (1,433 → 50 lines): split into 7 domain routers + deps/helpers/middleware/cache modules - `bot/telegram_bot.py` (3,151 → 394 lines): split into 6 mixins (approval, channels, timelimits, commands, activity, helpers) + declarative callback router - `youtube/extractor.py`: wrapped in `YouTubeExtractor` class with `ExtractorProtocol` for DI and mocking - Catalog cache now per-profile (multi-child setups no longer rebuild on every request) - Bonus-minutes logic deduplicated into `get_bonus_minutes()` helper **Fixed** - Missing `_channel_md_link` import in activity mixin (crash on `/watch` command) - Heartbeat dedup now keyed by `(video_id, profile_id)` — prevents cross-profile time tracking suppression - PIN comparison guards against `None` stored PIN (previously raised `TypeError`) - Channel unallow/unblock callback_data truncated to Telegram's 64-byte limit with prefix-match fallback - Session cookie set to `SameSite=Strict` - Removed dead imports and unused `YouTubeExtractor.timeout` parameter **Added** - 241 automated tests (pytest): utils, config, video_store, child_store, extractor, callback router, web deps, web integration - `REFACTOR.md` documenting the full refactor with metrics and confidence score ## v1.25.0 - 2026-02-25 **Added** - Avatar customization: tap the header avatar badge to open a dropdown with 16 emoji icons and 8 background colors - Selections persist per-profile in the database and update instantly without page reload - Login profile picker shows each child's custom avatar icon and color - "Switch Profile" link in avatar dropdown (multi-profile setups) - `POST /api/avatar` endpoint with allowlist validation (rate limited 10/min) ## v1.24.2 - 2026-02-25 **Changed** - Mobile header: search bar replaced with Search button that opens a floating overlay - Profile avatar shown as rounded-square initial badge in header (all screen sizes) - Desktop header: search bar centered between logo and profile avatar - Search results no longer include YouTube channel/playlist entries (fixes broken thumbnails) ## v1.24.1 - 2026-02-24 **Fixed** - Login redirect loop when only one profile exists and it has a PIN - `/pending`, `/approved`, `/stats`, `/logs`, `/search`, `/channel`, `/revoke` now scoped per-child profile (previously always operated on default profile) - All inline button callbacks (unallow/unblock, resend, channel menu/filter/page, starter import, pagination) now carry profile_id for correct routing ## v1.24.0 - 2026-02-24 **Added** - `/time`, `/shorts`, `/watch` commands now scoped per-child profile — each child gets independent time limits, schedules, bonus minutes, and Shorts settings - `/time setup` wizard preserves child context through all steps (button and custom text flows) **Fixed** - `/time off` no longer overridden by `config.yaml` defaults (string vs integer fallback guard) **Changed** - Config-level time limit fallbacks (`time_limits.*` in YAML) only apply to the "default" profile — new profiles start with no restrictions - Mode switch confirmation callbacks (`switch_confirm`) include profile_id for correct scoping ## v1.23.0 - 2026-02-24 **Added** - Multi-child profile support — each child gets isolated videos, channels, watch history, and time limits - `/child` command for profile management (`add`, `remove`, `rename`, `pin` subcommands) - Profile picker login flow ("Who's Watching?" → optional PIN entry per child) - Cross-child auto-approve: when a video is already approved for one child, the notification for another shows an "Auto-approve" button - `ChildStore` wrapper that scopes all `VideoStore` operations to a single profile - Profile badge in web UI header with "Switch" link when multiple profiles exist - `profiles` table with full CRUD and cascade delete **Changed** - All database tables (`videos`, `channels`, `watch_log`, `search_log`) now include a `profile_id` column — existing databases auto-migrate on startup - Unique constraints updated: `videos(video_id)` → `videos(video_id, profile_id)`, `channels(channel_name)` → `channels(channel_name, profile_id)` - Callback data format: `action:video_id` → `action:profile_id:video_id` (legacy 2-part format still accepted) - Channel cache is now per-profile - Time limits, schedule windows, and category budgets resolve per-profile - Telegram notifications include child name when multiple profiles exist - "Time's Up" page shows child name (e.g., "Alex has used..." instead of "You've used...") - PIN auth middleware rewritten for profile-based sessions (`child_id` / `child_name` in session) - Auto-creates "default" profile on first startup (inherits PIN from config) ## v1.22.0 - 2026-02-24 **Added** - `/filter` top-level command — manage word filters that hide matching video titles everywhere (catalog, Shorts, Your Requests, search results) - Word filters now apply globally, not just to search results **Changed** - `/search` simplified to show search history directly (was `/search history`); supports `/search [days|today|all]` - Removed `/search filter` subcommand — use `/filter add|remove ` instead ## v1.21.2 - 2026-02-24 **Fixed** - Channel matching throughout backend now uses `channel_id` (YouTube's stable unique identifier) instead of `channel_name` (mutable display name) — fixes "Your Requests" showing videos from allowlisted channels when YouTube changes the channel's display name - SQL JOINs for watch-time-by-category and watch breakdown use `channel_id` - `is_channel_allowed` / `is_channel_blocked` prefer `channel_id` lookup with name fallback - Bulk operations (`set_channel_videos_category`, `delete_channel_videos`) match on `channel_id` with name fallback for legacy rows - Channel cache and catalog builder keyed by `channel_id` - Backfill loop periodically resolves missing `channel_id` and `@handle` on channels and videos ## v1.21.1 - 2026-02-23 **Changed** - `/help` command now links to GitHub docs instead of the self-hosted help page (always works regardless of `base_url` config) **Docs** - Rewrote `docs/telegram-commands.md` — organized into sections, removed `/denied` (not implemented), added all missing commands (`/approved `, `/channel unallow|unblock`, `/search`, `/stats`, `/logs`, `/shorts`) ## v1.21.0 - 2026-02-23 **Added** - "Your Requests" grid section on homepage — shows recently-approved videos the kid explicitly searched for (excludes auto-approved channel videos), limited to 5 with "Show More" pagination - `/approved ` — fuzzy search approved videos by title or channel name; without args lists all approved videos as before **Changed** - `/channel unallow` now deletes all DB videos from that channel (cleanup on removal) - Renamed "Your Videos" → "Channel Videos" in the main grid section to distinguish passive channel feed from explicit requests - Channel Videos initial load reduced from 24 to 12 (with Show More for pagination) - Category filter pills show/hide cards in both Your Requests and Channel Videos sections - Schedule banner phrasing: "Videos available tomorrow at 9:00 AM" (was doubling "at at") ## v1.20.1 - 2026-02-23 **Fixed** - Include `starter-channels.yaml` in Docker image — `/channel starter` was showing "No starter channels configured" because the file was excluded by `.dockerignore` **Docs** - Refreshed README Features section to cover all features through v1.20 (Shorts, thumbnail previews, starter channels, per-day schedules, setup wizard, update notifications, help page) - Added `utils.py` and `starter-channels.yaml` to README Project Structure ## v1.20.0 - 2026-02-22 **Added** - GitHub release check: background task checks for new releases every 12 hours and sends a one-time Telegram notification to the admin with release notes and upgrade link - Notification is sent once per installation — loop stops permanently after notifying **Fixed** - Outside-hours unlock time now shows tomorrow's actual start time instead of incorrect value ## v1.19.1 - 2026-02-22 **Improved** - Polished feedback messages across bot and web UI for clearer, more actionable communication - Bot: "Unauthorized" → "This bot is for the parent/admin only." via new `_require_admin()` helper - Bot: Empty states (pending, approved), revoke flow, channel resolution, category management, search filters, time limits, and setup wizard now include context and next steps - Web: Warmer child-facing copy on denied, outside-hours, time's-up, and pending pages - Web: More specific error messages for invalid video links and fetch failures ## v1.19.0 - 2026-02-22 **Added** - YouTube Shorts support: detect Shorts via `/shorts/` URL pattern in yt-dlp results - Dedicated Shorts row on homepage — horizontal scroll with portrait 9:16 thumbnail cards - Channel cache now fetches `/shorts` tab alongside `/videos` tab for allowlisted channels - Portrait 9:16 player on watch page for Shorts (centered, max-width 480px) - "Short" badge on search results and homepage Shorts cards - `/shorts [on|off]` Telegram command to toggle Shorts row visibility (persisted in DB) - `shorts_enabled` config key under `youtube:` (default: true) - `/api/catalog?shorts=true` endpoint for Shorts catalog - `[SHORT]` label in Telegram approval notifications with `youtube.com/shorts/` link - `is_short` column in videos table (auto-migrated, existing videos default to 0) - `get_approved_shorts()` DB method for querying approved Shorts **Behavioral** - Shorts never appear in the main video grid — they only appear in the dedicated Shorts row when enabled - When Shorts are disabled (`/shorts off`), Shorts are hidden everywhere: catalog, search results, and channel filters ## v1.18.0 - 2026-02-22 **Added** - `/help` web page at `http://:8080/help` — standalone dark-mode command reference for all Telegram bot commands (no PIN required) - `/help` bot command includes a clickable "Full command reference" link when `BRG_BASE_URL` is set - `BRG_BASE_URL` env var for LAN links in Telegram messages; `deploy.sh` auto-detects host IP **Fixed** - Callback handler: added video_id regex validation in catch-all branch (defense-in-depth) - `_cb_switch_confirm`: guarded `int()` calls with `.isdigit()` checks to prevent unhandled ValueError ## v1.17.0 - 2026-02-22 **Added** - `/time setup` now shows top-level [Limits] [Schedule] menu - Schedule wizard with two paths: - "Same for all days": start presets → stop presets → done summary - "Customize by day": 7-day grid → per-day start/stop pickers → back to grid (configured days marked) - Custom time input in wizard via text reply with `parse_time_input()` validation **Fixed** - `parse_time_input()` now accepts hour-only formats (8am, 12pm, 9pm) — previously required minutes - Schedule wizard Custom buttons now work (wrapped prompts in `_md()` for MarkdownV2 escaping) ## v1.16.0 - 2026-02-22 **Added** - Per-day schedule overrides: set different time windows and limits for each day of the week (e.g. `/time mon start 8am`, `/time sat edu 120`) - Day override copy command: `/time mon copy weekdays` copies Monday's settings to Tue-Fri - `/time setup` guided wizard with inline buttons for choosing between simple (one daily cap) and category (edu + fun) limit modes - Mode switch warnings: switching from category to simple (or vice versa) prompts with inline confirmation buttons before changing - `/time` now shows today's status with progress bars plus a 7-day weekly overview - `/time ` shows effective settings for that specific day **Behavioral** - Setting a flat limit now auto-clears category limits (and vice versa) to prevent conflicts - Per-day override "off" clears the override (falls back to default), unlike default "off" which disables the limit - Web enforcement (`_get_time_limit_info`, `_get_category_time_info`, `_get_schedule_info`) now resolves per-day overrides automatically - `/watch` command uses per-day resolved limits for progress display ## v1.15.0 - 2026-02-21 **Added** - Search cards now show view count below channel name (e.g. "2.3M views") - Thumbnail preview cycling: hover (desktop) or scroll-into-view (tablet) cycles through YouTube auto-generated thumbnails with crossfade and progress dots - Preview engine supports dynamically loaded cards (catalog pagination, channel/category filters) ## v1.14.1 - 2026-02-21 **Security** - Validate `video_id` against regex on `/api/status/` endpoint (prevents DB probing with arbitrary strings) - Bind watch heartbeat to session — only the video loaded on `/watch` can send heartbeats (prevents cross-video time inflation) - Validate callback data: `chan_filter`/`chan_page` status checked against allowlist, `logs_page`/`search_page` days clamped to 1-365 - Validate `video_id` in thumbnail URL fallback construction (defense-in-depth for yt-dlp output) - Separate empty-PIN logic from HMAC check for clarity and correctness - Fix misleading status labels when `allowchan`/`blockchan` pressed on already-resolved videos ## v1.14.0 - 2026-02-21 **Changed** - `/channel` now shows Allowed/Blocked menu with summary stats and side-by-side buttons - Filtered channel views with pagination and 📋 Channels home button - All pagination uses consistent ◀ Back / Next ▶ buttons with disabled placeholders - Internal: extracted `_nav_row`, `_edit_msg`, `_channel_resolve_and_add`, `_channel_remove` helpers (-68 lines) ## v1.13.1 - 2026-02-21 **Changed** - Welcome message now prompts with inline Yes/No buttons instead of auto-sending starter channels - Starter channels list paginated (10 per page) with Show more/Back navigation ## v1.13.0 - 2026-02-21 **Added** - Starter channels: ~15 curated kid-friendly YouTube channels available on first boot and via `/channel starter` (closes #9) - Per-channel Import buttons with check mark feedback for already-imported channels - Welcome message on `/start` and first-run (empty DB) explaining the bot's purpose - `/channel starter` command always available for browsing and importing starter channels ## v1.12.5 - 2026-02-20 **Added** - `/watch N` now trims to available data range and shows a hint when fewer days exist (e.g. "Only 3 days of data available — try `/watch 3`") ## v1.12.4 - 2026-02-20 **Fixed** - Fix `/watch yesterday` and `/watch N` commands crashing due to passing timezone string instead of `ZoneInfo` object to `datetime.now()` **Added**