つみノート
概要
勉強・記録・復習をひとつにまとめた、iPhone / iPad 向けの学習管理アプリです。ポモドーロ・テクニックの集中タイマー、Markdown ノート、間隔反復(FSRS)アルゴリズムによる復習、学習記録を 1 つのアプリにまとめています。資格試験や語学に限らず、どんな学習にも使えます。
2026年10月に App Store で公開しました。 企画、デザイン(Figma)、LP、モバイルアプリ、同期 API まで、1 人で作りました。
- App Store:https://apps.apple.com/jp/app/%E3%81%A4%E3%81%BF%E3%83%8E%E3%83%BC%E3%83%88/id6821588887
- LP:https://tsumi-note.hosokawa-lab.dev/
- Figma : https://www.figma.com/design/2YR8QWeewgVF9tIM9yfLu7/%E5%AD%A6%E7%BF%92%E3%82%A2%E3%83%97%E3%83%AA%EF%BD%9CBlue---Charcoal?node-id=0-1&t=NFFKjW0KqciAPDn5-1
主な機能
- 集中タイマー:ポモドーロ・テクニック(初期設定は25分集中+5分休憩×4ラウンド)。集中は1〜180分、休憩は1〜60分、ラウンドは1〜12回で設定でき、終了は通知で知らせます。集中した時間はそのまま学習記録になります
- Markdown ノート:見出し・リスト・チェックリスト・画像。保存は自動、フォルダー分けとピン留めに対応
- 間隔反復の復習:「もう一度・難しい・普通・簡単」の4段階で評価すると、FSRS が次に出題するタイミングを自動で決めます。1問ごとの制限時間、苦手な問題の抽出も
- 学習記録:曜日ごとの学習時間グラフ、正答率・時間切れの集計、今週復習すべき問題の自動ピックアップ
- アカウントと同期:アカウントなし(ゲスト)でもすべての主要機能が使えます。アカウントを作ると iPhone と iPad の間で同期できます
Web LP(公開中)
| 分類 | 技術 |
|---|---|
| フレームワーク | TanStack Start(TanStack Router + Vite) |
| 言語 | TypeScript 7(ネイティブコンパイラ) |
| スタイル・UI | Tailwind CSS v4 / shadcn/ui(Radix UI) |
| 実行環境 | Cloudflare Workers(SSR) |
| メール送信 | Resend(お問い合わせフォーム) |
| 開発ツール | Vite / Vitest + Testing Library / Lefthook / Bun |
先に Figma でデザインを作り込み、それを忠実にコードへ再現しました。アプリの完成に合わせて Figma を更新し、LP もそれに追従させています。Figma の色変数は、そのまま Tailwind のトークンにしています。
- アプリ画面のモックアップをコードで描画:画像を使わず HTML と CSS で組み、390×844 を基準に縮小表示しています。どの解像度でもぼやけず、軽量です。
- エッジで SSR:Cloudflare Workers で配信し、起動時間は 16ms。無料枠の範囲で運用できます。
- アプリへの導線:App Store へのリンクに加え、Safari では画面上部にアプリを開くバナーを表示します。
- 品質を保つ仕組み:フォーマット・lint・型検査を 1 コマンドで実行。入力チェックはブラウザとサーバーで共通にし、bot 対策の隠し入力欄も設けています。レスポンシブとアクセシビリティにも対応しています。
モバイルアプリ(App Store で公開中)
| 分類 | 技術 |
|---|---|
| フレームワーク | Expo(SDK 57)+ React Native 0.86 / React 19 / Expo Router |
| 言語 | TypeScript(strict) |
| スタイル | NativeWind v4(LP と同じ Figma のデザイントークン) |
| 端末内の DB | expo-sqlite + Drizzle ORM |
| 認証 | Better Auth(@better-auth/expo)+ expo-secure-store |
| 復習アルゴリズム | FSRS(ts-fsrs) |
| タイマー | 開始・終了時刻から残り時間を計算 + expo-notifications |
| テスト | Jest(jest-expo)/ DB のテストは better-sqlite3(約560件) |
| ビルド・配布 | 手元の Mac の Xcode で Archive → TestFlight → App Store |
Anki にも採用されている FSRS(間隔反復アルゴリズム)を使っています。データはまず端末の SQLite に保存する「ローカルファースト」の設計なので、ログインしなくてもすべての主要機能がオフラインで使えます。
- バックグラウンドでもずれないタイマー:毎秒カウントするのではなく、開始・終了の時刻から残り時間を計算します。アプリをバックグラウンドにしたり再起動したりしても、正しい状態に戻ります。
- アカウントごとに DB ファイルを分離:ゲストとアカウントで SQLite のファイルを分けています。全テーブルに ownerId を持たせる方式に比べ、条件の書き漏れによるデータ漏えいが構造上起きません。ゲストのデータは、同意を得てからアカウントへ引き継ぎます。
- 認証情報の保護:トークンは Keychain(端末だけに保存、iCloud には同期しない)に置き、DB や AsyncStorage には保存しません。
同期・認証 API(本番運用中)
| 分類 | 技術 |
|---|---|
| 実行環境・ルーティング | Cloudflare Workers + Hono |
| 認証 | Better Auth(メール+パスワード / Sign in with Apple / Google) |
| DB | Cloudflare D1 + Drizzle ORM |
| 画像ストレージ | Cloudflare R2 |
| メール送信 | Resend(メール確認・パスワード再設定) |
| テスト | Bun test(約140件) |
ローカル DB(端末の SQLite)とサーバー(D1)はどちらも SQLite 系です。そこで、テーブル定義や型など Drizzle の共通部分は共有しつつ、DB 接続・マイグレーション・同期処理はそれぞれ分けています(React Native 側の SQLite ドライバと Cloudflare D1 では、Drizzle の接続方法が異なるため)。
- 差分同期:ローカルの変更は同じトランザクション内で送信待ちの一覧(Outbox)に登録し、まとめて送ります。送信には一意の ID(opId)を付けているので、同じ送信が何度届いても処理は 1 回だけです。受け取る側は、サーバーが付けた通し番号(server_seq)をもとに差分だけを取得します。端末の時計には依存しません。
- データを失わない競合解決:学習履歴は ID で重複を除いて両方残します。ノートや問題が同時に編集された場合は、上書きされる側を「競合コピー」として保存します。削除済みのデータが古い端末からの送信で復活することもありません。
- 画像の重複排除:ノート画像は SHA-256 をキーに R2 へ保存し、同じ画像は 1 つだけ保存します。配信は Worker 経由で、ユーザーごとに認可します。
- セキュリティ:メール確認が必須、IP 単位のレート制限、登録済みかどうかを推測されない応答、パスワードポリシーを用意しています。ユーザー ID は常にセッションから取得します。アカウント削除では D1 と R2 の両方からデータを消し、残ったファイルは定期実行(Cron)で掃除します。
全体を通して
- TypeScript で Web・アプリ・サーバーを構築し、Drizzle のテーブル定義や型を共有
- Cloudflare(Workers・D1・R2)だけで運用し、コストを抑制
- オフライン優先の設計で、アカウントなしでも使える
- Figma のデザイントークンを Web とアプリで共有
- 型検査・lint・テストをコミット前に自動で実行(全体で約700件)
アピールポイント
-
個人で企画からリリースまでやり切った 要件定義、Figma でのデザイン、LP、iOS / iPadOS アプリ、同期 API、App Store の審査を経て公開するまで、すべて 1 人で行いました。利用規約・プライバシーポリシー・アカウント削除など、ストア公開に必要な要件にも対応しています。
-
ローカルファーストと同期の両立 オフラインでもすぐ動く体験と、複数端末での同期を両立させました。Outbox、冪等な送信、サーバー側の通し番号、競合コピーといった仕組みで、通信が途切れても、同時に編集しても、ユーザーのデータを失わない設計にしています。
-
学習の効果を考えた機能設計 ポモドーロ・テクニックで集中した時間が自動で記録に残り、FSRS で覚えた内容を忘れる直前に復習できます。「集中する → 記録する → 復習する」が 1 つのアプリでつながっています。
-
セキュリティとプライバシーへの配慮 アカウントごとに DB ファイルを分ける、トークンを Keychain に置く、登録済みかどうかを推測されないようにする、レート制限をかける、アカウント削除時にサーバーのデータを確実に消す、といった対策を取っています。広告・解析の仕組みは入れていません。
-
低コストでの運用 LP・API・DB・画像ストレージを Cloudflare にまとめ、個人開発でも続けやすい構成にしました。
-
テストで支える品質 アプリ・API・LP で合計約 700 件のテストを書いています。同期の競合や削除の境界条件までテストで確認しています。