注意

この記事の内容は、開発途中のものです。 今後、変更などを行う可能性が高いのでご注意ください。

はじめに

「睡眠日誌」は、日々の睡眠を24時間のタイムラインに記録するWebアプリです。

タイムラインは正午から翌日の正午までを30分刻みの48スロットに区切り、スロットをタップして睡眠や在床覚醒(床に就いたが眠れていない時間)を塗っていきます。 記録は一覧やグラフで振り返れるほか、受診のときに見せる帳票として印刷やPDFで出せます。

CakePHP 5、PHP 8.3、MySQL 8.0で作り、招待制でsleep.223n.techに公開しています。 アプリの全体像は、睡眠日誌アプリ「sleep-diary-php」の紹介にまとめています。

今週は0.31.0から0.33.0まで、4つの版を出しました。

日付 主な内容
0.31.0 2026-09-14 招待リクエストを受け付けたら、招待を自動で発行して送る
0.32.0 2026-09-17 ホーム画面に追加する手順のページ、画面の端の余白の調整
0.32.1 2026-09-18 ホーム画面アプリの見た目が前の版のまま変わらない問題を修正
0.33.0 2026-09-18 Appleのヘルスケアの睡眠を、参考用の帯として重ねて表示

この記事では、それぞれの版で何を変えたか、どこで迷ったかを記録します。

招待リクエストを受けたら、その場で登録の案内を送る

これまでの流れと、変えた理由

睡眠日誌には通常の新規登録の画面がなく、招待リンクからだけ登録できます。 まだ登録していない人は、ログイン画面の「招待をリクエスト」からメールアドレスを送って申し込みます。

0.30系までは、申し込みを「未対応」として記録し、管理者へ通知するだけでした。 管理者が管理画面で招待を発行して、初めて登録用のメールが届きます。 申し込んだ人は、管理者が気付くまで待つしかありませんでした。

0.31.0からは、受け付けた時点で招待(有効期限は14日)を発行し、登録用のURLをメールで送ります。 管理者には「招待を自動送信しました」という通知が事後に届き、必要なら招待の管理画面から取り消せます。

誰にでもメールを送れる窓口にしないために

自動で送るようにすると、入力されたアドレスへ無条件にメールが届きます。 他人のアドレスを入れれば、この窓口を使って誰にでもメールを送れてしまいます。

そこで、送信の上限を3つの層に分けました。 送信元ごとの上限、同じアドレスへ送り直すまでの間隔、サービス全体で自動発行できる数の上限です。

次の場合は自動での発行を見送り、これまでどおり未対応として管理者に回します。

  • 過去に却下したアドレスからの申し込み(管理者が却下と判断したものを、自動の処理で覆さないため)
  • サービス全体の上限に達している場合
  • 招待の発行やメールの送信に失敗した場合(発行した招待を取り消してから未対応に戻す)

画面の応答は、どの場合も同じ「受け付けました」にしています。 画面の表示から、アドレスが登録済みかどうかや、メールを送れたかどうかを推測されないようにするためです。 登録用のURLも画面には出しません。 他人のアドレスで申し込んだ第三者の手元へ、URLが渡ってしまうためです。

届くメールの注意書きも、心当たりがなければリンクを開かずに破棄してほしいという内容に改めました。

ホーム画面から開くアプリとして使う

追加の手順をまとめたページ

睡眠日誌は、スマートフォンのホーム画面に追加するとアプリのように使えます。 全画面で開けて、電波のない場所でも記録でき、記録を促すリマインダーの通知も受け取れます。

ただし、iPhoneではブラウザーが追加を自動で案内してくれません。 追加の操作は共有メニューの中にあります。 Safari以外のブラウザーから追加できるかどうかは、iOSの版やブラウザーによって異なります。 通知が届くのはiOS 16.4以降で、ホーム画面のアイコンから開いた場合だけです。

こうした条件を利用者が自力で調べるのは大変なので、0.32.0で手順をまとめたページ(/install)を用意しました。 ログインしなくても開けるページで、画面下部の「アプリとして使う」からたどれます。 iPhoneとiPadはSafariでの手順で、ほかにAndroidのChrome、パソコンのChromeとEdgeの手順も載せています。

画面の端の余白と表示名

ホーム画面から開いたときに画面全体を使えるよう、ページを画面の端まで表示する指定(viewport-fit=cover)にしました。 そのままでは、ノッチやホームインジケーターと重なる位置に、文字やボタンが出てしまうおそれもあります。 そこで、ヘッダーと本文とフッターに、セーフエリアの分の余白を足しました。 セーフエリアに対応していないブラウザーでは、これまでどおりの余白のままです。

ホーム画面のアイコンの名前は「睡眠日誌」に固定しました(apple-mobile-web-app-title)。 指定しないと、ページのタイトルから作った長い名前になるためです。

ステータスバーを透過させる指定(black-translucent)も検討しましたが、採用しませんでした。 睡眠日誌のヘッダーは画面の上に固定していないので、スクロールするとステータスバーの白い文字が本文に重なって読めなくなるためです。

更新しても見た目が変わらない問題(0.32.1)

0.32.0を公開したあと、ホーム画面アプリの見た目が前の版のまま切り替わりませんでした。 本番のブラウザーで確かめると、新しい版のキャッシュの中に、前の版のCSSが入っていました。

原因は、Service Workerが事前に取得するCSSやJavaScriptを、クエリの付かないURLで取りに行っていたことです。 静的ファイルは長期間キャッシュしてよいという指定で配信しているため、クエリの付かないURLの中身はCDNに長く残ります。 キャッシュの版数を上げて取り直させても、迂回できるのはブラウザー側のキャッシュだけで、その先にあるCDNが古いファイルを返していました。

対策として、取得するURLに版数のクエリ(?v=版数)を付けました。 CDNがまだ持っていないURLになるので、配信元から新しいファイルを取り直せます。 キャッシュに保存するときの名前はクエリなしのまま据え置き、キャッシュから返すときの照合の仕組みは変えていません。

利用者から見ると、アプリを一度閉じて開き直せば新しい表示になります。

Appleのヘルスケアの睡眠を参考として重ねる(0.33.0)

どういう機能か

Apple Watchや睡眠アプリがiPhoneのヘルスケアに記録した睡眠の区間を、記録画面のタイムラインに参考用の帯として重ねる機能です。 日誌そのものは、これまでどおり自分で塗ります。 ヘルスケアの値は機器による推定なので、帯はそのまま写すためではなく、体感と見比べながら塗るための手がかりとして置いています。

ヘルスケアのデータを記録画面へ渡す方法

Webページからヘルスケアのデータを直接読む公式の方法はなく、サーバーから取りに行くAPIもありません。 そこで、iOSの「ショートカット」でデータを書き出してクリップボードへコピーし、記録画面へ貼り付ける方式を採りました。

ショートカットは次の3つのアクションで作ります。

  1. 「ヘルスケアサンプルを検索」で、睡眠分析を過去3日分、開始日の降順に最大400件取り出す
  2. 「テキスト」で、開始日と終了日と値とソースの4つを---の行で区切って並べる(それぞれに全件がまとめて入る)
  3. 「クリップボードにコピー」

手順書を作る過程で、つまずきやすい点がいくつか見つかりました。

  • 昇順に並べると、件数が上限を超えたとき、新しい側(いちばん見たい前夜の分)が切れる
  • 期間(Duration)の値は、開始と終了の差より1秒短く出ることがあったため使わない
  • 日付の書式を手で組み立てると年や12時間制の扱いでずれやすいので、ISO 8601のプリセットを選ぶ
  • 「URLを開く」アクションで睡眠日誌を開こうとすると、ホーム画面アプリではなく既定のブラウザー(初期設定ではSafari)で開き、ログイン状態も別になる

また、iPhoneがロックされている間はヘルスケアを読めないため、寝ている間に自動で取り込むことはできません。 朝起きてから、ショートカットを実行して貼り付ける使い方になります。

貼り付けた内容はサーバーに送らない

ヘルスケアのデータは健康に関わる情報なので、サーバーには送らないことにしました。

貼り付け欄はフォームの外に置き、送信される項目にもしていません。 この配置が崩れていないかは、テストで確かめています。 貼り付けた内容は端末のブラウザーにだけ保存します。 対象の日から7日を過ぎた分は、記録画面を開いたときに自動で消えます。 「参照データを削除」ボタンでも消せます。

睡眠の記録本体には入れないので、記録の保存や集計を担うサーバー側の処理は変えていません。 あわせて、プライバシーポリシーに端末の中だけに保存するものの説明を足しました。

帯の見せ方

帯は「睡眠」「覚醒」「臥床(寝床にいた時間)」の3種類だけにし、色は付けていません。 睡眠は塗りつぶし、覚醒は枠線だけ、臥床は内側の細い線で描き分けます。

ヘルスケアには「コア」「深い」「レム」といった睡眠の段階もありますが、帯では区別しません。 帯の幅は10px未満しかなく、模様を付けても見分けられないためです。 色で分けると、日誌の状態を表す既存の色と取り違えるおそれもあります。 段階ごとの内訳は、読み取った区間の一覧を開くと時刻つきで確かめられます。

帯と一緒に、その日の睡眠時間の合計も出します。 ヘルスケアの区間は入れ子になっていて、たとえば「就寝」の区間が、その中の「睡眠」の区間を含んでいます。 単純に足すと臥床の時間が倍近くになるため、重なりを除いてから合計しています。

仮眠は記録されるのか

手順書の注意書きは、最初の版と今の版で内容が変わっています。

最初の版には「睡眠スケジュールの時間帯の外の仮眠は、取りこぼすことがある」と書いていました。 「1時間以上の睡眠しか記録されない」という説明を前提にしていたためです。

ところが、実機で確かめると、1時間に満たない睡眠の区間も記録されることが分かりました。 そこで注意書きを、昼や夕方の仮眠も記録されること、ただし睡眠スケジュール(睡眠フォーカス)の設定によっては取りこぼす可能性が残ることに書き直しました。

おわりに

今週の睡眠日誌では、招待リクエストを受けたらその場で登録の案内を送るようにし、iPhoneでアプリのように使うための案内を整え、ヘルスケアの睡眠の記録を見ながら日誌を付けられるようにしました。

ヘルスケアの参照表示では、健康に関わるデータをサーバーへ送らないことを先に決め、その範囲で使いやすさを詰めました。 ショートカットの手順は、実機で試すたびに直すところが見つかったので、今後も使いながら手を入れていきます。

更新の内容は、睡眠日誌の更新履歴でも公開しています。