← 一覧に戻る

dev-timer

GitHub ↗ TypeScript 最終push: 2026/8/8 13:07

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 管理外)
  • /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.com13.230.63.19
    • ntfy トピック: dev-timer-ichirokisanuki-{random12} 形式で確定(実値は両環境の .env のみ)
    • iPhone の ntfy アプリで購読済 / e2e 通知到達確認済(CLI から add --in 2m → 2分後に着信)
  • 設定ファイルの真実のコピーを 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-tracked topic 付与済(dev-tracker の一覧に出る)

次にやること(Phase 1: Backend MVP)

  1. package.json と TypeScript セットアップ
  2. データモデル定義(SQLite スキーマ)
  3. Hono サーバー実装
  4. ntfy 連携
  5. CLI
  6. ローカルでの動作確認

次にやること(Phase 2: サーバーデプロイ)

  1. Lightsail 側準備(Node.js / /opt/dev-timer/ / SQLite / systemd / Nginx / certbot / DNS / cron)
  2. ntfy トピック名を決定
  3. iPhone に ntfy アプリ install + topic 購読

次にやること(Phase 3: Web UI)

  1. Astro フロントエンド
  2. デプロイ(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: checksync に置換、同期失敗時の注意表示追加。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.com13.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.exampleDEV_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 listen
  • bin/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 を有効にしているので parseDurationm[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-tracked topic 付与
  • 引き継ぎ用に CLAUDE.md / WIP.md / DECISIONS.md / ROADMAP.md を充実

次回やること

  • Phase 1(Backend MVP)から着手: package.json と TypeScript セットアップ → Hono サーバー → SQLite スキーマ → ntfy 連携 → CLI

詰まったこと / 気づき

  • なし(着手前)

最近のコミット

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.tsbuildEventFromTimer が完全ボディを生成)
  • 同期は一方向(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 側の前提(初回のみ):

  1. GCP でサービスアカウントを作成し、Calendar API を有効化、JSON 鍵を取得(リポジトリ外に保存)
  2. Google カレンダーで専用カレンダーを作成し、SA のメールアドレスに「予定の変更」権限で共有
  3. そのカレンダーのデフォルト通知を「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)。