No description
  • Swift 79.3%
  • JavaScript 11.1%
  • Python 4.8%
  • Shell 4%
  • Makefile 0.8%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-04 14:30:17 +05:30
.agents/skills/agent-wiki docs: scaffold repo-local agent wiki routing 2026-06-24 17:12:46 +05:30
.claude/skills/agent-wiki docs: scaffold repo-local agent wiki routing 2026-06-24 17:12:46 +05:30
.wiki Add admin next-photo control for clock widgets 2026-08-04 14:30:17 +05:30
docs Merge branch 'docs/release-gate-update' 2026-07-29 13:20:06 +05:30
Protos wip: reflect Apple Notes checklist checkbox state on the dashboard 2026-06-23 21:14:54 +05:30
Resources feat(release): add make install, tag-aware debug builds, .dmg packaging + admin build-id 2026-06-25 15:17:59 +05:30
scripts perf(build): cache downloaded clock photos across worktrees 2026-08-04 13:28:17 +05:30
Sources/TVDashboard Add admin next-photo control for clock widgets 2026-08-04 14:30:17 +05:30
Tests Add admin next-photo control for clock widgets 2026-08-04 14:30:17 +05:30
.gitignore feat(clock): manifest-driven photos + on-demand image streaming 2026-06-28 18:15:59 +05:30
AGENTS.md docs: scaffold repo-local agent wiki routing 2026-06-24 17:12:46 +05:30
CLAUDE.md docs: scaffold repo-local agent wiki routing 2026-06-24 17:12:46 +05:30
Makefile feat(kiosk): promote the local Space to a first-class runtime surface 2026-06-30 01:02:16 +05:30
Package.resolved wip: reflect Apple Notes checklist checkbox state on the dashboard 2026-06-23 21:14:54 +05:30
Package.swift feat(clock): photo-backed clock with shuffling photo sets 2026-06-24 19:18:19 +05:30
README.md Add admin next-photo control for clock widgets 2026-08-04 14:30:17 +05:30

TV Dashboard

A small macOS app that turns a TV into a read-only "kiosk" dashboard built from widgets — a big digital clock and one or more of your Apple Notes — arranged on a grid you configure from a control window on your Mac. The default layout shows the clock above two notes side by side (Day 0 and Day 1). The TV just displays; you drive everything from the Mac.

It's designed for a TV you reach over AirPlay (in this setup: a Hisense TV that appears on the network as Living Room), used in Extend mode so the TV is a second display rather than a mirror of your Mac.


What you need (prerequisites)

  • A Mac running macOS 14 (Sonoma) or newer. (Built and tested on macOS 26 with Xcode 26 / Swift 6.3.)
  • Xcode or the Xcode Command Line Tools — this is what provides the swift compiler and make. If you don't have them:
    xcode-select --install
    
  • Apple Notes with a note titled Day 0 (or whatever note you want to show).
  • The Mac and the TV on the same Wi-Fi / LAN.

No external packages — it builds entirely with the Swift toolchain.


Build & run

From the project folder, the one-liner is:

make run

That compiles the app, assembles TV Dashboard.app, and launches it. The control window opens on your Mac.

Other targets:

Command What it does
make build Just compile (no bundle).
make app Build and assemble .build/TV Dashboard.app.
make run Build, bundle, and open the app.
make install Build, write a dist/*.dmg, install into ~/Applications, and relaunch the kiosk (only if the running instance is a debug build).
make release Build a signed, distributable .dmg under dist/ (see Releases).
make clean Delete build artifacts.

Run it as the bundled app (make run), not via swift run. macOS attaches the "control Notes" permission to the app bundle and its identity, so launching the real .app is what gives you a clean permission prompt that sticks.

You can also open the folder in Xcode directly (File → Open → Package.swift) and press Run, if you prefer.


Releases: install from a .dmg

If you were handed a TV Dashboard-<version>.dmg (rather than building from source):

  1. Double-click the .dmg to mount it, then drag TV Dashboard.app onto the Applications alias in the window.
  2. The app is self-signed, not notarized by Apple, so the first time you open it macOS warns that it's from an unidentified developer. Get past it once, either way:
    • Right-click the app → Open, then click Open in the dialog, or
    • run xattr -dr com.apple.quarantine "/Applications/TV Dashboard.app" in Terminal first.
  3. After that first launch it opens normally. Continue with the Apple Notes permission prompt below.

On your own machine you can skip the manual steps: make install builds the app, writes the .dmg to dist/, copies the app into ~/Applications with quarantine already cleared, and relaunches the kiosk if the running instance is a debug build. The admin panel's bottom-right footer shows the build identity (e.g. v1.1.0+a1b2c3d · build 94) so you can tell a dev build from a clean release at a glance.

Maintainers: the release .dmg is produced with make release — see docs/releasing.md.


First run: the Apple Notes permission prompt

The first time the app tries to read your notes, macOS shows:

"TV Dashboard" wants access to control "Notes".

Click OK (Allow). That's what lets the dashboard read the Day 0 note.

  • If you dismissed it or clicked Don't Allow, either press "Grant Notes Access…" in the control window, or go to System Settings → Privacy & Security → Automation → TV Dashboard and turn on Notes.
  • The control window's Status panel shows Apple Notes access: Granted once it's working.

Connect the TV over AirPlay (Extend)

AirPlay is connected manually:

  1. Open Control Center (menu-bar icon, top-right).
  2. Open Screen Mirroring.
  3. Select Living Room.
  4. Choose "Use As Separate Display" (Extend) — not Mirror — if macOS asks.
  5. Back in the app, press "Start Kiosk on External Display."

The dashboard appears full-screen on the TV. The button only enables once macOS sees a second display, so connect AirPlay first.

The control window's Status panel also tells you whether Living Room is currently reachable on the network (Bonjour discovery) and whether an external display is present, so you can sanity-check both before starting the kiosk.


Configure the dashboard (widgets & layout)

Everything is driven from the Dashboard Layout panel in the control window — the TV itself is read-only.

The dashboard is a vertical stack of rows; each row holds one or more widgets side by side. Two widget types ship today:

  • Clock — 12/24-hour, optional seconds/date, and optional photo-backed sets. While a photo clock is running, its control-panel Next photo button advances that clock immediately; the action is runtime-only and does not change the saved layout.
  • Note — shows an Apple Note. Pick it by typing its exact name, or press "Refresh Note List" and choose from the per-widget menu. Optional title header and a font-size scale.

Use the row/widget controls to add, remove, reorder, and resize: the height/width steppers set each row's / widget's share of the screen; "Add Row" adds a band, the + on a row adds a widget to it, and the trash buttons remove. "Load default (clock + 2 notes)" restores the starting layout.

Edits apply to the TV automatically (a moment after you stop changing things) and are saved to ~/Library/Application Support/TV Dashboard/config.json, so the layout persists across launches.


Verify live updates

  1. With the kiosk running on the TV, open Day 0 in Apple Notes on your Mac.
  2. Type something.
  3. It appears on the TV within about 12 seconds (the app re-reads the note once a second).
  4. The "Last note update" time in the control window also advances.

How it renders

The whole dashboard is one CSS grid sized to exactly the TV screen (100vh × 100vw): nothing scrolls and nothing spills past the screen edge. Each widget lives in its own cell; if a note is taller than its cell it's clipped with a soft fade at the bottom as a "there's more" cue rather than scrolling. Widgets update independently — a note edit patches only that note's cell, so the clock never flickers and the layout never jumps.

Inside a note cell it renders the structure used in notes like Day 0:

  • Bold text (section titles like TO DO, House TODO).
  • Blank-line spacing.
  • Numbered lists (Rishav Todo).
  • Checklist rows with hollow circles (the unordered lists).

The TV surface is strictly read-only: no text selection, no menus, no links, and the mouse pointer is bounced back to your Mac's display if it wanders onto the TV.

Not in V1 (see docs/handoff-v1.md): images, attachments, links, tables, and reliable checklist checked/unchecked state — Apple's public scripting interface doesn't expose whether a checklist item is ticked, so V1 shows every checklist item the same way. Recovering checked-state is planned for a later version.


Troubleshooting

The TV isn't discovered ("Living Room: Not found")

  • Make sure the TV is on and on the same Wi-Fi as the Mac.
  • Some networks block Bonjour/mDNS between devices ("AP/client isolation"). Try the same Wi-Fi band, or a network where the Mac can see the TV.
  • You can still connect: open Control Center → Screen Mirroring and look for Living Room there. The dashboard only needs the second display to run the kiosk; Bonjour status is a convenience check.

Notes permission denied

  • System Settings → Privacy & Security → Automation → TV Dashboard → enable Notes.
  • If TV Dashboard isn't listed, press "Grant Notes Access…" in the app to trigger the prompt, then allow it.
  • Builds are signed with a stable self-signed identity (the TVD Local cert), so the grant normally sticks across rebuilds. If that cert isn't installed the build falls back to ad-hoc signing, whose identity changes with the code — so macOS may re-prompt after a rebuild; just allow it again. (Distribution .dmgs use the same self-signed identity and are not notarized — see docs/releasing.md.)

No second display after connecting AirPlay

  • Make sure you chose Extend ("Use As Separate Display"), not Mirror.
  • Give it a few seconds — the "Start Kiosk…" button enables automatically when the display appears (the app watches for display changes).
  • If the TV connected as a mirror, open System Settings → Displays and set the TV to "Use as separate display" / extend.
  • If the external display goes away while the kiosk is up, the app tears the kiosk down cleanly on its own.

The dashboard stopped updating after I edited the note

A note's name in Apple Notes is its first line. The app finds each note widget's note by name the first time (e.g. Day 0), but then tracks it by a stable internal id, so after that you can rename the heading and the dashboard keeps following it. The only thing that breaks the link is the very first lookup: if no note has that exact first line (e.g. you'd already renamed it), that cell shows a "Loading …" placeholder. Fix: set the widget's note name to the note's current first-line text in the Dashboard Layout panel — from then on, edits (including future renames) refresh live. If updates still don't appear, check the "Last note update" time in the control window: if it's advancing, the app is reading fine; if it's frozen, see the error line and the Notes-permission steps above.

macOS notification suppression / screen-sharing indicators (observe & document)

When you AirPlay/extend to a TV, macOS may change notification behavior and show screen-sharing-style indicators. For V1, observe and note what your Mac does:

  • Whether a Focus / Do Not Disturb turns on automatically while mirroring/extending (System Settings → Focus, and the Control Center Focus tile).
  • Whether a purple/orange screen-recording or AirPlay indicator appears in the menu bar.
  • Whether notification banners still appear on your Mac (primary) display.

V1 uses Extend (a second display), not Mirror, which generally avoids the "mirroring to TV" Focus trigger — but confirm on your setup and jot down what you see. Actively preventing DND/notification suppression is planned for a later version; V1 only documents it.


Stopping / quitting

  • Press "Stop Kiosk" to take the dashboard off the TV (keeps the control window).
  • ⌘Q quits the app (this also stops the kiosk and releases the keep-awake power assertion).
  • Closing the control window quits the app when the kiosk isn't running.

While the kiosk is active, the Mac is kept from sleeping its display (a power assertion), so the TV doesn't go dark mid-display.


Developer notes

  • Architecture: native Swift / AppKit + SwiftUI. Notes are read in-process via NSAppleScript (confined to a serial queue) behind a NotesSource protocol, so a deeper Notes reader can be swapped in later.
  • Widget rendering: the TV is a single locked-down WKWebView that loads a static CSS-grid shell once (DashboardPageBuilder). Swift then drives it through KioskBridge, which pushes the layout (applyConfig) and per-cell data (applyData) as base64-encoded JSON over evaluateJavaScript — so updates patch only the changed cell, with no reload. Adding a widget type = a case in WidgetProps, a WidgetRenderer branch, and a JS branch.
  • Config: the layout is a Codable DashboardConfig (rows → widgets with fr weights
    • per-widget props), edited in the admin window and persisted by ConfigStore to ~/Library/Application Support/TV Dashboard/config.json.
  • Headless checks: make test runs both suites (no GUI). make selftest (swift run TVDashboard --selftest) covers the Swift rendering/config/bridge-encoding logic; make jstest extracts the real shell JavaScript from DashboardPageBuilder and runs it (against a stubbed DOM, fed the real --dump-config output via Node) to guard the Swift↔JS contract — applyConfig/applyData logic, not real CSS/layout. Needs Node.
  • Layout/structure:
    Sources/TVDashboard/
      main.swift            app entry
      AppDelegate.swift     windows
      AppState.swift        state + multi-note polling + bridge push
      Config/               DashboardConfig (layout model) + ConfigStore (JSON persistence)
      Notes/                NotesSource protocol + AppleScript reader
      Render/               DashboardPageBuilder (web shell), KioskBridge, WidgetPayload,
                            NoteHTMLRenderer (note sanitizer), KioskWebView
      Views/                KioskView (TV surface), AdminView (control panel + layout editor)
      Kiosk/                DisplayManager, KioskWindow, PowerManager
      Net/                  BonjourBrowser