dev-timer
WIP(現在進行中)
Work In Progress
このプロジェクトで現在進行中の作業と、過去のスナップショットを記録する。
現在の状況
ステータス: 大幅ブラッシュアップ完了。ntfy を撤去し Google カレンダー同期へ全面移行、本番反映済み(2026-08-08)。
完了したこと
- Phase 1〜3: Backend(Hono + SQLite)+ Lightsail 本番 + Web UI(Astro)稼働中(詳細はアーカイブ参照)
- Google カレンダー同期移行(DECISIONS 2026-08-08 参照):
- SA
dev-timer-gcal@ichirokisanuki.iam.gserviceaccount.comが専用カレンダー「dev-timer」(共有済・デフォルト通知 0分前)へイベントを upsert src/gcal.ts(fetch 直叩きの薄クライアント)、/api/sync-calendar(バックフィル + 残骸掃除)、gcal_event_id列追加- check-due / crontab 毎分発火 / ntfy を撤去。crontab は日次 04:15 の sync-calendar に置換
- 本番バックフィル済(pending 4件 → イベント化確認済)、本番 e2e(create→done→delete)PASS
- 鍵の所在: ローカル
~/ichirokisanuki/secrets/dev-timer-sa-key.json/ サーバー/opt/dev-timer/dev-timer-sa-key.json(600、git 管理外)
- SA
/api/health追加(Nginx 構成で/healthが 404 になる問題の恒久対応)- README 全面記載、CLAUDE.md / deploy/README.md を実態に同期
次にやること
- なし(通知 e2e も 2026-08-08 13:06 発火分で iPhone 到達確認済み。移行は完全に完了)
- 任意: GitHub Actions 自動デプロイ(現状は手動 rsync、deploy/README.md に手順記載)
- 任意: Lightsail 側のリソース監視(memory tight、~150MB 余裕しかない)
運用上の既知の特性
- 発火数分前に作った直近タイマーは通知が落ちることがある(iPhone 側のカレンダー同期が発火に間に合わないため。12:47 のテストで実証)。時間〜日単位の通常運用では問題なし。ntfy(即時 push)との既知のトレードオフとして受容済み
詰まっていること
なし
過去のWIPアーカイブ
(新しい「現在の状況」を書く前に、古いものをここに追記でアーカイブする。新しいものが上)
2026-08-08 11:51 より前のスナップショット(Phase 2 完了時点)
ステータス: Phase 1(Backend MVP)+ Phase 2(Lightsail デプロイ)完了。本番運用開始。
完了したこと
- Phase 1: Hono + SQLite + ntfy + CLI を実装、ローカル動作確認 PASS
- Phase 2:
https://dev-timer.ikapps.comで本番稼働中- systemd
dev-timer.service(deploy ユーザー / port 3001 / Restart=on-failure / メモリ ~57MB) - DB:
/var/lib/dev-timer/dev-timer.db - Nginx + Let's Encrypt(HTTPS、HTTP→HTTPS 301、自動更新)
- deploy ユーザーの crontab で毎分
/api/check-dueを発火 - DNS: Route 53 で A レコード(
dev-timer.ikapps.com→13.230.63.19) - ntfy トピック:
dev-timer-ichirokisanuki-{random12}形式で確定(実値は両環境の.envのみ) - iPhone の ntfy アプリで購読済 / e2e 通知到達確認済(CLI から
add --in 2m→ 2分後に着信)
- systemd
- 設定ファイルの真実のコピーを
deploy/配下で git 管理(dev-timer.service/nginx/dev-timer.ikapps.com)
次にやること(当時のメモ)
- Phase 3: Web UI(Astro)→ その後のセッションで実装・デプロイ済み(上の「現在の状況」参照)
2026-04-27 17:50 時点のスナップショット
ステータス: 設計合意済み・プロジェクト初期化のみ完了。実装はこれから。
完了したこと
- 要件と設計の合意(dev-tracker と分離する判断、技術スタック確定)
~/cdev/dev-timer初期化(git, .devnotes/, README, CLAUDE.md)- GitHub private リポ作成 + push
dev-trackedtopic 付与済(dev-tracker の一覧に出る)
次にやること(Phase 1: Backend MVP)
- package.json と TypeScript セットアップ
- データモデル定義(SQLite スキーマ)
- Hono サーバー実装
- ntfy 連携
- CLI
- ローカルでの動作確認
次にやること(Phase 2: サーバーデプロイ)
- Lightsail 側準備(Node.js /
/opt/dev-timer// SQLite / systemd / Nginx / certbot / DNS / cron) - ntfy トピック名を決定
- iPhone に ntfy アプリ install + topic 購読
次にやること(Phase 3: Web UI)
- Astro フロントエンド
- デプロイ(GitHub Actions → rsync)
詰まっていること
なし(実装着手前)
ROADMAP(計画)
ロードマップ
今週
- Phase 1: Backend MVP
- package.json + TypeScript + Hono + better-sqlite3 セットアップ
- SQLite スキーマ定義(timers テーブル)
- REST API 実装(CRUD + check-due)
- ntfy.sh 送信ロジック
- CLI 実装(add / list / done)
- ローカル動作確認
- Phase 2: サーバーデプロイ
- Lightsail に Node.js install
- systemd unit 作成・起動
- Nginx vhost + certbot で SSL
- DNS A レコード追加(Route 53、AWS CLI)
- crontab で
/api/check-dueを毎分叩く - ntfy トピック名確定 + iPhone 購読
- エンドツーエンド疎通確認
今月
- Phase 3: Web UI
- Astro セットアップ(dev-tracker のスタイル流用)
- 一覧 / 作成フォーム / done / delete
- GitHub Actions で自動デプロイ(現状は手動 rsync)
- ブラッシュアップ: ntfy → Google カレンダー同期(2026-08-08 完了)
- 連携方式の設計・決定(SA + カレンダー共有。DECISIONS 参照)
- タイマー CRUD ↔ カレンダーイベント同期の実装
- 本番反映 + バックフィル(iPhone 通知の最終確認のみ残)
今四半期
- dev-tracker の詳細ページから「このプロジェクトの open フォローアップ」リンク追加(dev-timer の API を叩く)
いつか
- 繰り返しタイマー(毎週月曜 9:00 など)
- 通知履歴の振り返りページ(「今月発火したフォローアップは N 件」など)
取り下げ(ntfy → Google カレンダー方針転換により)
- スヌーズ機能(通知時に「1時間後にもう一度」) → カレンダー上でイベントを動かせば足りる想定。必要なら再検討
- 通知に Action ボタン(done/snooze)を埋め込む(ntfy のクリックアクション)
- Slack / メール通知も選択可に
DECISIONS(意思決定)
意思決定記録
このプロジェクトで下した重要な意思決定を記録する。 最新が上に来る。
2026-08-08: 通知チャネルを ntfy から Google カレンダー同期に置き換える
背景: ntfy は「通知が一度飛んで終わり」で、未消化のフォローアップの見通しが悪い。大幅ブラッシュアップの方向性として、タイマーを Google カレンダーの予定として可視化したいという要望。
決定: ntfy を完全撤去し、タイマー CRUD のたびに専用カレンダー「dev-timer」へイベントを upsert する。通知はカレンダーのデフォルト通知(0分前ポップアップ)+ iPhone の Google カレンダー公式アプリで受ける。
- 連携方式は GCP サービスアカウント + カレンダー共有(OAuth や GAS ブリッジではなく)。トークン失効がなく運用が最も安定。SA は
dev-timer-gcal@ichirokisanuki.iam.gserviceaccount.com、スコープはcalendar.eventsのみ(最小権限) - 依存は
google-auth-libraryのみ。Calendar v3 REST は fetch 直叩き(googleapisはサイズ過大で不採用) - DB を source of truth とする一方向同期。イベントボディは常に
buildEventFromTimer()で完全生成して patch する(= upsert、冪等)。カレンダー側の手動編集は取り込まない(双方向同期は v1 スコープ外) - done → タイトルに「✓ 」を付けて残す(カレンダーが履歴になる)。cancelled / 削除 → イベントも削除
- check-due / crontab 毎分発火 /
fired遷移は廃止(status enum とCHECK制約にfiredは既存行の履歴として残す)。代わりに日次 04:15 の/api/sync-calendarで自己修復 - 同期失敗時の非対称ハンドリング: POST/PATCH はベストエフォート(
gcal.syncedで通知)、DELETE のみ fail-hard(イベント削除に失敗したら DB 行を残して 502)
理由: 個人ツールとして「予定表に載る」ことが通知より価値が高い。SA 方式は初回セットアップ(GCP + カレンダー共有)だけ済ませれば無期限で動く。落とし穴として、カレンダー側で手動削除(ゴミ箱行き)されたイベントは patch が 404 にならず cancelled のまま成功するため、ボディに常に status: 'confirmed' を含めて復活させる実装にした(e2e で発見・対処済み)。
2026-04-27: ntfy トピック名は dev-timer-ichirokisanuki-<12文字> 形式
背景: ntfy.sh は subscribe にトークンが要らず誰でも購読可能なため、トピック名自体が実質的なシークレットになる。一方で覚えやすさも欲しい。
決定: 接頭辞を dev-timer-ichirokisanuki- に固定し、接尾辞を node -e 'crypto.randomBytes(9).toString("base64url")' で生成(base64url 12 文字、英数 + -_)。実値はサーバーの /opt/dev-timer/.env とローカルの .env(どちらも gitignore 済)にのみ保管し、git には残さない。
理由: 接頭辞でプロジェクト・所有者を識別、接尾辞 12 文字で 70bit 弱のエントロピーが入るので推測攻撃に十分耐える。これに加えて「タイマー本文に詳細コンテキストを書かない」方針(既存 DECISIONS)を併用すれば、漏れても被害は限定的。
2026-04-27: 設定ファイルの真実のコピーは deploy/ 配下で git 管理
背景: systemd unit と Nginx vhost をサーバー側 (/etc/systemd/system/ / /etc/nginx/sites-available/) に直接置く運用は、設定の追跡性が悪く再現性も低い。dev-tracker は GitHub Actions YAML だけリポ管理で、サーバー側設定はリポ外(multi-purpose-lightsail-server1 の DEVLOG / DECISIONS にメモするのみ)。
決定: dev-timer ではプロジェクトリポ直下の deploy/ に systemd unit と Nginx vhost を置き、サーバー側はそれを scp/install で配置する運用にする。サーバー側で certbot 等が書き換えた場合は、ローカルにコピーを取り直して反映させる。
理由: dev-timer はサーバー常駐型サービスなので、systemd の再現は dev-tracker(静的ファイル rsync のみ)より重要。ローカルで diff 取れる・PR レビューできる利点が大きい。deploy/ 配下にあれば「アプリのソースとは別物」という意図も明確(誤って npm 系のパスと混同しない)。
2026-04-27: ローカル CLI のデフォルト DEV_TIMER_API は本番 URL
背景: CLI は本番運用が主、ローカル開発時のサーバー起動はたまにしか発生しない。デフォルトをどちらに振るかで普段の使い勝手が変わる。
決定: .env / .env.example のデフォルトを https://dev-timer.ikapps.com にする。ローカル開発で API を立ち上げて叩きたいときは、DEV_TIMER_API=http://127.0.0.1:3001 npx tsx bin/dev-timer.ts ... のように env で一時上書き する運用。
理由: 普段の dev-timer add --in 3d "..." は本番に書き込むのが意図したい挙動。ローカル開発のために env を毎回設定するより、開発時だけ env を一時的にセットする方が頻度的に合理的。逆向きの設定(ローカルがデフォルト)だと、CLI を本番運用に使うたびに env 必要で煩わしい。
2026-04-27: dev-tracker と分離して新規プロジェクトにする
背景: 開発中の follow-up リマインダ機能を dev-tracker に組み込むか、別プロジェクトにするか議論。
決定: 別プロジェクト dev-timer として分離し、~/cdev/dev-timer に作成する。
理由: dev-tracker は「GitHub から pull した read-only スナップショット」、dev-timer は「ユーザーが書き込む follow-up タスク」。データソースもライフサイクルも違うので、同居させると概念がブレる。dev-tracker は静的サイト(ビルド済rsync)で済むが dev-timer はサーバー常駐が必要、という運用面の差も大きい。
2026-04-27: iPhone 通知は ntfy.sh を使う
背景: 通知方法の候補は ntfy.sh / Pushover / Slack DM / Telegram bot。
決定: ntfy.sh を採用。
理由: OSS、無料、curl 1発で送信可能、iOS app あり。Pushover は安定だが $5 課金で買い切るほど大規模に使う想定ではない。Slack/Telegram はチャンネル設計が必要で大袈裟。トピック名は推測しにくい長い文字列にすれば実質プライベート(誰でも subscribe できる仕様への対処)。
2026-04-27: データはサーバー側(Lightsail)に置く
背景: タイマーの永続化先は Mac か Lightsail か。
決定: Lightsail 側に SQLite で持つ。
理由: push 通知のために常時走る cron が必要。Mac はスリープしたら cron が動かないので不可。Lightsail は常時稼働で既に他サービスを動かしているので、リソース面でも自然。CLI からは API 経由で書き込む。
2026-04-27: アクセス制御は付けない
背景: タイマー本文に SQL や Issue 参照などの開発コンテキストが入る場合のリスクを議論。
決定: Basic 認証 / API キー等の認証は付けない(dev-tracker と同様、URL を知っている人は誰でも見られる)。
理由: ユーザー方針: 「通知はただアラートだけで、実行する内容を詳細にかかないから大丈夫」。タイマー本文は「ALTER TABLE の効果確認」程度の薄いラベルに留めるため、認証コストを払わない。後から必要になったら追加可能。
2026-04-27: バックエンドは Hono + SQLite
背景: ランタイム選定。Express / Fastify / Hono、DB は SQLite / PostgreSQL / file-JSON。
決定: Hono + better-sqlite3。
理由: Hono は軽量・型安全・モダンで、個人ツールに最適。SQLite はファイル1個で済み運用が楽、個人スケール(タイマー数百〜千件)なら全く問題ない。Postgres は overkill、JSON ファイルは同時書き込み制御が面倒。
2026-04-27: フロントは Astro(dev-tracker と統一)
背景: Web UI のフレームワーク選定。
決定: Astro を採用。
理由: dev-tracker と同じスタックで揃えると学習コスト 0、コンポーネント・スタイルを流用しやすい。dev-timer は SSR 寄りだが、Astro は server endpoints を持てるのでバックエンドへの fetch も自然。
2026-04-27: ポート 3001 を使う
背景: 既存サービスとのポート衝突回避。
決定: 3001 で起動。
理由: 既存: 7channel=8001, aix=5001, mydb=php-fpm。3000番台は空き、3001 が見やすい。
DEVLOG(作業ログ)
開発日誌
このプロジェクトでの作業を時系列で記録する。 最新のエントリが上に来る。
2026-08-08
12:55 - 大幅ブラッシュアップ: ntfy → Google カレンダー同期へ全面移行(本番反映済み)
やったこと
- 実装(DB を source of truth とする一方向 upsert 同期):
src/gcal.ts新規: JWT(SA・calendar.eventsスコープ)+ Calendar v3 REST を fetch 直叩き。10s タイムアウト、429/5xx は1回リトライ、404/410 は「イベント消滅」として型付きエラーsrc/server.ts: POST/PATCH でイベント upsert(patch は常に完全ボディ)、cancelled でイベント削除(失敗時は event_id 保持で後日掃除)、DELETE のみ fail-hard。/api/check-due廃止、/api/sync-calendar新設(バックフィル + 残骸掃除、privateExtendedProperty で重複防止)、/api/health追加src/db.ts:gcal_event_id列を冪等 ALTER で追加。src/notifier.ts削除、config は GCAL_* に置換(未設定なら同期スキップでローカル開発可)- CLI:
check→syncに置換、同期失敗時の注意表示追加。Web UI: 期限超過バッジ、同期失敗トースト - 依存:
google-auth-library追加、better-sqlite3 11→12(ローカル Node 26 対応)、hono 4.13.1(脆弱性修正)
- Google 側セットアップ: Calendar API 有効化 + SA
dev-timer-gcal@作成 + 鍵発行は gcloud で自動化。カレンダー「dev-timer」作成・SA への「予定の変更」共有・デフォルト通知 0分前・iPhone アプリ表示 ON はユーザー手作業 - ローカル e2e(実カレンダー): insert / fire_at 変更 / done「✓」付与 / cancel / rm / sync 冪等性 / 手動削除イベントの復活、全シナリオ PASS
- 本番デプロイ: DB バックアップ(
pre-gcal-20260808.db)→ SA 鍵配置(600)→ .env に GCAL_* 追記 → rsync +npm ci --omit=dev+ restart →/api/sync-calendarで pending 4件(#36,#37,#32,#33)をバックフィル → 本番 e2e(create→done→delete)PASS → crontab の毎分 check-due を日次 04:15 sync-calendar に置換 → NTFY_* を .env から削除 → Web UI 配信 - ドキュメント刷新: README を実態に合わせて全面記載、CLAUDE.md / deploy/README.md 更新
詰まったこと / 気づき
- Google カレンダーの手動削除はゴミ箱行き(status=cancelled)で、events.patch が 404 にならずそのまま成功する(イベントは見えないまま)。ボディに常に
status: 'confirmed'を含めて patch で復活させる方式で解決。ゴミ箱から30日でパージされた後は 404/410 → 再 insert のフォールバックが効く - ローカル Node が v26 になっており better-sqlite3 11.x のビルドが不可 → 12.x へ更新(本番 Node 22 でも prebuilt あり問題なし)
- ローカルのバックグラウンドサーバー残骸で port 3199 が EADDRINUSE → 旧ビルドにリクエストが飛び e2e 結果を一度誤読しかけた。lsof で PID 確認してから kill する
通知 e2e の結果(13:10 追記)
- 12:47 発火分は未到達、13:06 発火分は到達。コード・作成経路は同一で、差は「イベント作成から発火までの余裕」と「iPhone アプリの同期タイミング」のみ
- 結論: 発火数分前に作る直近タイマーは iPhone 側の同期が間に合わず通知が落ちうる(Google カレンダーの特性、受容済みのトレードオフ)。通常運用(時間〜日単位)は問題なし
- テストデータは掃除済み(本番 timer #41 削除・孤児イベント削除)
次回やること
- 任意: GitHub Actions 自動デプロイ(ROADMAP 残項目)
11:51 - 未記録だった Phase 3(Web UI)をコミットで固定・ブラッシュアップ方針決定
やったこと
- セッション開始時に未コミットの変更(
web/一式・src/server.tsの CORS・Nginx vhost 変更)を発見。本番を確認したところ:GET https://dev-timer.ikapps.com/→ Astro 静的サイトが 200 で返る(/var/www/dev-timer/配信)/api/*→ Hono へのプロキシで正常稼働/healthは Nginx 404(新 vhost ではプロキシ対象が/api/のみのため。実害なしだが監視に使うなら要注意)- → Phase 3 は過去セッションで実装・本番デプロイ済みだが、git/devnotes に未記録だったと判明
- Phase 3 の内容を確認してコミット:
web/: Astro 5 静的サイト。一覧(pending / all タブ、60秒ポーリング)、作成フォーム(datetime-local ++30分〜+1週間プリセット)、done / cancel / 削除ボタン、トースト通知src/server.ts:/api/*に CORS 追加deploy/nginx/dev-timer.ikapps.com:/api/→ Hono プロキシ + それ以外は/var/www/dev-timerの静的配信、という構成に変更
.devnotes/(WIP / ROADMAP)を実態に合わせて更新
決めたこと
- 大幅ブラッシュアップの方向性: 通知チャネルを ntfy から Google カレンダー追加(同期)方式に置き換える(連携方式などの設計はこれから。決定次第 DECISIONS.md に記録する)
次回やること
- Google カレンダー連携の設計(連携方式の選定 → 実装 → 本番反映)
2026-04-27
18:25 - Phase 2: Lightsail デプロイ完了・iPhone 通知 e2e 確認
やったこと
- ntfy トピック名生成:
dev-timer-ichirokisanuki-{random12}形式で確定(node の crypto.randomBytes(9) を base64url 化)。実値は両環境の.envのみに保管 - iPhone に ntfy アプリ install + topic 購読 + 通知許可(ユーザー側で実施、curl publish で届くこと確認)
- Route 53 で A レコード追加:
dev-timer.ikapps.com→13.230.63.19(AWS CLI、UPSERT、伝播 INSYNC まで確認) - Lightsail(multi-purpose-lightsail-server1)への SSH 経由でのセットアップ:
- Node.js 22 LTS を NodeSource からインストール(v22.22.2 / npm 10.9.7)+ build-essential
/opt/dev-timer/・/var/lib/dev-timer/を deploy ユーザー所有で作成- ローカルから
/tmp/dev-timer-stageに rsync → サーバー側で sudo rsync で/opt/dev-timer/へ移動 + chown deploy - サーバー上で
npm ci+npm run build(better-sqlite3 のネイティブビルドも完走) /opt/dev-timer/.env配置(PORT=3001/DB_PATH=/var/lib/dev-timer/dev-timer.db/NTFY_TOPIC/NTFY_SERVER、600 / deploy 所有)dev-timer.serviceを/etc/systemd/system/に install + enable + start(active running、メモリ 57M)- Nginx vhost を
/etc/nginx/sites-available/に配置 + sites-enabled にリンク + reload certbot --nginx -d dev-timer.ikapps.comで Let's Encrypt SSL 発行(自動更新スケジュール込み)- deploy ユーザーの crontab に
* * * * * curl -sS -X POST http://127.0.0.1:3001/api/check-due > /dev/null 2>&1を追加
- e2e 確認: ローカル CLI(
DEV_TIMER_API=https://dev-timer.ikapps.com)からadd --in 2mで予約 → 約 2 分後に iPhone に通知到達 - 後処理:
- ローカル
.env/.env.exampleのDEV_TIMER_APIを本番 URL(https://dev-timer.ikapps.com)に変更 - certbot 適用後の Nginx vhost を
deploy/nginx/dev-timer.ikapps.comに反映(真実のコピーを git 管理)
- ローカル
決めたこと(DECISIONS.md にも転記)
- ntfy トピックは
node -e 'randomBytes(9).toString("base64url")'ベースで生成し、dev-timer-ichirokisanuki-<12文字>形式に統一 - サーバー側設定ファイル(systemd unit / Nginx vhost)は ローカル
deploy/配下に真実のコピーを置いて git 管理 - ローカル CLI のデフォルト
DEV_TIMER_APIは本番 URL にする(ローカル開発で API を起動したいときだけ env 上書き)
詰まったこと / 気づき
deployユーザーは ssh 鍵が違うので直接ログインできず、ubuntu経由 →/tmpに rsync → sudo rsync で/optに移動 + chown という二段構えで配置した- 初回
npm ciで deprecated 警告(prebuild-install)出たが better-sqlite3 11.x は prebuilt binary を引いて動作 OK 127.0.0.1直叩きのHost: dev-timer.ikapps.comで 404(default_server に流れた)が出たが、外部から FQDN で叩くと正しくルーティング → 実害なし
次回やること
- Phase 3: Web UI(Astro)。dev-tracker のスタックを流用
- 必要に応じて GitHub Actions で自動デプロイ(現状は手動 rsync)
18:00 - Phase 1: Backend MVP 実装(Hono + SQLite + ntfy + CLI)
やったこと
- TypeScript + ESM、Node.js 20+ 前提のスケルトンを作成
src/config.ts:dotenvで.env読み込み(PORT / DB_PATH / NTFY_TOPIC / NTFY_SERVER)src/db.ts: better-sqlite3 でtimersテーブル + WAL モード、status はpending/fired/done/cancelledの 4 値src/notifier.ts: ntfy.sh への POST(Title ヘッダは ASCII のみ許容なので非ASCII は RFC2047 で base64 エンコード)src/server.ts: Hono の REST API(POST /api/timers/GET /api/timers/PATCH /api/timers/:id/DELETE /api/timers/:id/POST /api/check-due)、zod でバリデーションsrc/index.ts:@hono/node-serverで port listenbin/dev-timer.ts: CLI(add --in 30m|2h|3d|1w/--at ISO8601/--project/list [--status]/done/cancel/rm/check).env.exampleと.gitignore(SQLite 生成物*.db/*.db-shm/*.db-walを追加)整備- ローカル動作確認: add → list → check-due(過去日 timer が
firedに遷移)→ done → rm まで一通り PASS、tsc --noEmitもエラーなし - commit & push(
bf01466)
気づき
noUncheckedIndexedAccess: trueを有効にしているのでparseDurationでm[1]!のような non-null assertion を明示的に書く必要があった- Phase 2 の rsync 戦略を見越して
dist/は git 管理外、deploy/配下に systemd unit / Nginx vhost のひな形を置く方針も同時に固めた
次回やること
- Phase 2: Lightsail デプロイ
17:45 - プロジェクト発足・設計合意・初期化
やったこと
- dev-tracker のセッション中に follow-up リマインダの実装を相談、別プロジェクトとして分離する判断
- 設計合意:
- 通知: ntfy.sh
- データ: Lightsail (SQLite)
- インターフェース: CLI + Web UI(Astro)
- 認証: なし(タイマー本文に詳細コンテキストを書かない方針)
- ポート: 3001
~/cdev/dev-timer初期化(new-project-setup skill 経由)- git init / .gitignore / README / CLAUDE.md / .devnotes/ 4ファイル
- GitHub private リポ作成
dev-trackedtopic 付与
- 引き継ぎ用に CLAUDE.md / WIP.md / DECISIONS.md / ROADMAP.md を充実
次回やること
- Phase 1(Backend MVP)から着手: package.json と TypeScript セットアップ → Hono サーバー → SQLite スキーマ → ntfy 連携 → CLI
詰まったこと / 気づき
- なし(着手前)
最近のコミット
- bdf629a 通知 e2e 完了を記録(直近タイマーの同期ラグ特性も devnotes に追記) 2026/8/8
- 5930dba ntfy を撤去し Google カレンダー同期へ全面移行(本番反映済み) 2026/8/8
- 4226cb2 Phase 3: Astro Web UI(本番デプロイ済み分)をコミットし devnotes を実態に同期 2026/8/8
- 985bd33 Phase 2 デプロイ完了の記録と deploy/ 配下の設定ファイル整備 2026/4/27
- bf01466 Backend MVP(Hono + SQLite + ntfy + CLI)を実装 2026/4/27
- d57eb98 引き継ぎ用に CLAUDE.md と .devnotes/ 4ファイルを充実 2026/4/27
- be9cbd3 Initial commit 2026/4/27
README
dev-timer
概要
開発作業中の「N時間後/N日後にこれをチェックしたい」というフォローアップを記録し、Google カレンダーにイベントとして同期するツール。通知はカレンダーのデフォルト通知(0分前)+ iPhone の Google カレンダー公式アプリで受ける。
ユースケース例:
ALTER TABLE 実行 → 3日後に slow query 改善確認
デプロイ後 → 24時間後にエラー率確認
仕様変更の影響 → 翌週に効果測定
公開URL: https://dev-timer.ikapps.com/ (Web UI + API)
構成: Hono (port 3001) + SQLite / Astro 静的サイト / Nginx / systemd / Lightsail
アーキテクチャ
- DB (SQLite) が source of truth。タイマーの作成・変更・完了のたびに、対応するカレンダーイベントを upsert する(
src/gcal.tsのbuildEventFromTimerが完全ボディを生成) - 同期は一方向(dev-timer → カレンダー)。カレンダー側でイベントを編集・削除しても DB には反映されない
- ステータスとイベントの対応:
pending→ 通常イベント(fire_at から15分枠)done→ タイトル先頭に「✓ 」を付けて残す(履歴)cancelled/ タイマー削除 → イベントも削除
- 同期失敗はリクエストを落とさずレスポンスの
gcal.syncedで通知し、POST /api/sync-calendar(日次 cron でも実行)が後から自己修復する
セットアップ
npm install
cp .env.example .env # GCAL_CALENDAR_ID / GCAL_SA_KEY_PATH を設定(未設定なら同期スキップで起動可)
npm run dev # API サーバー (port 3001)
cd web && npm install && npm run dev # Web UI(/api は 3001 にプロキシ)
Google 側の前提(初回のみ):
- GCP でサービスアカウントを作成し、Calendar API を有効化、JSON 鍵を取得(リポジトリ外に保存)
- Google カレンダーで専用カレンダーを作成し、SA のメールアドレスに「予定の変更」権限で共有
- そのカレンダーのデフォルト通知を「0分前」に設定し、iPhone の Google カレンダーアプリで表示を ON
使い方
CLI
npm run cli -- add --in 3d --project myapp "インデックス追加の効果確認"
npm run cli -- add --at 2026-09-01T10:00:00+09:00 "四半期レビュー準備"
npm run cli -- list [--status pending|fired|done|cancelled|all]
npm run cli -- done <id>
npm run cli -- cancel <id>
npm run cli -- rm <id>
npm run cli -- sync # 未同期の pending を一括登録・cancelled の残骸イベント掃除
DEV_TIMER_API 環境変数で接続先を切替(デフォルトは .env の本番 URL)。
API
| Method | Path | 説明 |
|---|---|---|
| GET | /api/timers?status=&limit= |
一覧(デフォルト pending) |
| POST | /api/timers |
作成(fire_at ISO8601 / project / context) |
| PATCH | /api/timers/:id |
部分更新(status / context / fire_at) |
| DELETE | /api/timers/:id |
削除(カレンダーイベントも削除) |
| POST | /api/sync-calendar |
再同期(バックフィル + 残骸掃除) |
| GET | /api/health |
ヘルスチェック |
作成・更新系のレスポンスには gcal: { synced, error? } が付く。
デプロイ
deploy/README.md を参照(手動 rsync + systemctl restart dev-timer)。