3.2 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Build & Deploy
# Build (TypeScript → bundled JS)
npm run build
# Watch mode during development
npm run dev
# Deploy to Stream Deck after building
cp com.pdma.notion-timer.sdPlugin/bin/plugin.js "$HOME/Library/Application Support/com.elgato.StreamDeck/Plugins/com.pdma.notion-timer.sdPlugin/bin/plugin.js"
pkill -f "notion-timer" # Stream Deck restarts the process automatically
# Full install (first time — copy plugin folder, not symlink; Stream Deck doesn't follow symlinks)
cp -r com.pdma.notion-timer.sdPlugin "$HOME/Library/Application Support/com.elgato.StreamDeck/Plugins/"
There is no test suite. Manual testing is done by pressing buttons in the Stream Deck app and checking the Notion database.
Architecture
The plugin has two distinct runtime contexts that communicate via WebSocket (managed by the SDK):
Plugin process (src/plugin.ts → bin/plugin.js) runs in Node.js 20 under Stream Deck:
- One
TimerToggleclass (extendsSingletonAction) handles all button instances of the action settingsCache(Map keyed byaction.id) is required because the SDK doesn't expose other actions' settings — it must be updated after everysetSettings()call, not just on appear- Single-timer enforcement: when starting a timer, iterate
this.actionsand stop any other active timer viasettingsCache - Global settings (token, DB IDs, user ID) are stored as hardcoded
DEFAULTSinplugin.ts
Property Inspector (ui/property-inspector.html) runs in a browser frame inside the Stream Deck app:
- Uses Elgato's official
$PIlibrary (files inui/libs/) — do not modify these files - The libs were copied from an installed Time Tracker plugin; they are not in npm
- Settings are saved via
$PI.setSettings(), not a custom WebSocket implementation - Event listeners must be attached inside
$PI.onConnected(), notDOMContentLoaded - Projects are sent from the plugin to the PI via
streamDeck.ui.sendToPropertyInspector()whenonPropertyInspectorDidAppearfires (plugin pushes, PI doesn't pull)
Critical SDK v2 patterns (different from v1 examples online):
- Use
streamDeck.ui.sendToPropertyInspector()notev.action.sendToPropertyInspector() - Use
streamDeck.ui.onSendToPlugin()notSingletonAction.onSendToPlugin(the method never fires in v2) SingletonAction.onDidReceiveSettingsalso does not reliably fire — settings come via$PI.onDidReceiveSettingson the PI side
Notion API (src/notion.ts):
- Notion pages have icons of
type: "emoji"(direct emoji string) ortype: "icon"(named SVG icon withname+colorfields) - Named icons are mapped to emoji via lookup tables:
book+color → colored book emoji,skip-forward+color → ⏭+circle, others inICON_NAME - Timer entries: created with
Status: "Running", patched withStatus: "Stopped"andEnd Time
Debug
Stream Deck logs are at ~/Library/Logs/ElgatoStreamDeck/. The plugin process stderr also appears there.
The streamDeck.ui.onSendToPlugin handler in plugin.ts still writes to /tmp/sd-debug.log — this should be removed once debugging is no longer needed.