Hosokawa Lab
HomeAboutSkillsLogProjectsBlogContactLogin
Projects
Project

つみノート

2026-09-2512 min read

概要

勉強・記録・復習をひとつにまとめた、iPhone / iPad 向けの学習管理アプリです。ポモドーロ・テクニックの集中タイマー、Markdown ノート、間隔反復(FSRS)アルゴリズムによる復習、学習記録を 1 つのアプリにまとめています。資格試験や語学に限らず、どんな学習にも使えます。

2026年10月に App Store で公開しました。 企画、デザイン(Figma)、LP、モバイルアプリ、同期 API まで、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
Info

先に 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
Tip

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件)

アピールポイント

  1. 個人で企画からリリースまでやり切った 要件定義、Figma でのデザイン、LP、iOS / iPadOS アプリ、同期 API、App Store の審査を経て公開するまで、すべて 1 人で行いました。利用規約・プライバシーポリシー・アカウント削除など、ストア公開に必要な要件にも対応しています。

  2. ローカルファーストと同期の両立 オフラインでもすぐ動く体験と、複数端末での同期を両立させました。Outbox、冪等な送信、サーバー側の通し番号、競合コピーといった仕組みで、通信が途切れても、同時に編集しても、ユーザーのデータを失わない設計にしています。

  3. 学習の効果を考えた機能設計 ポモドーロ・テクニックで集中した時間が自動で記録に残り、FSRS で覚えた内容を忘れる直前に復習できます。「集中する → 記録する → 復習する」が 1 つのアプリでつながっています。

  4. セキュリティとプライバシーへの配慮 アカウントごとに DB ファイルを分ける、トークンを Keychain に置く、登録済みかどうかを推測されないようにする、レート制限をかける、アカウント削除時にサーバーのデータを確実に消す、といった対策を取っています。広告・解析の仕組みは入れていません。

  5. 低コストでの運用 LP・API・DB・画像ストレージを Cloudflare にまとめ、個人開発でも続けやすい構成にしました。

  6. テストで支える品質 アプリ・API・LP で合計約 700 件のテストを書いています。同期の競合や削除の境界条件までテストで確認しています。

#TanStack Start#TypeScript#Tailwind CSS#Cloudflare Workers#Expo#React Native