Idea, Concept, and Technology

アイデアを形に

つぎつぎタイマー プライバシーポリシー / Privacy Policy

最終更新: 2026年9月12日

このアプリは一切の情報を収集しません。

データの収集と送信

つぎつぎタイマーはネットワーク通信を行いません。作成したチェーン (テンプレート)、実行中のタイマー、設定は、お使いの端末の中にだけ保存され、開発者や第三者に送信されることはありません。アカウント登録もありません。

広告とトラッキング

広告は表示しません。広告 SDK、解析 SDK、トラッキング技術は一切含まれていません。

アラームの許可について

タイマーの終了をお知らせするために、iOS の「アラームとタイマー」(AlarmKit) の許可を使用します。この許可はタイマーを鳴らす目的以外には使いません。

アプリ内課金 (応援)

「開発者を応援する」の購入は Apple の App Store を通じて処理されます。購入に関する情報は Apple のプライバシーポリシーに従って扱われ、このアプリが受け取るのは「購入が完了した」という結果だけです。支援しても機能は変わりません。

お子様のプライバシー

年齢を問わず、いかなる個人情報も収集しません。

変更

このポリシーを変更する場合は、このページを更新します。

お問い合わせ

ご質問はサポートページからお願いします。


Privacy Policy (English)

Last updated: September 12, 2026

This app collects no information at all.

Data collection and transmission

Tsugitsugi Chain Timer never connects to the network. Your chains (templates), running timers, and settings are stored only on your device and are never sent to the developer or to anyone else. There is no account.

Advertising and tracking

The app shows no ads and contains no advertising SDKs, analytics SDKs, or tracking technology.

Alarm permission

The app uses the iOS "Alarms & Timers" permission (AlarmKit) only to notify you when a timer ends.

In-app purchases (tips)

"Support the Developer" purchases are processed by Apple's App Store. Purchase information is handled under Apple's privacy policy; the app only learns that a purchase completed. Tips don't change any features.

Children's privacy

We collect no personal information from anyone, of any age.

Changes

If this policy changes, this page will be updated.

Contact

Questions are welcome via the support page.

分身が日本の鉄道を勝手に旅して日記を書くサービスをClaude Codeで作って公開した

はじめに

分身のキャラクターが日本の実在の鉄道路線を勝手に旅して、途中下車した駅の名物や車窓の風景を一人称の日記に書いて届けてくれる、というWebサービスを作って公開しました。

bunshin-tabi.com

企画からデータ処理、日記の生成、OGP画像、独自ドメインでの公開まで、ほぼすべてを Claude Code と対話しながら作りました。いわゆる「操作する旅」ではなく、旅かえるのように「分身が勝手にした旅の報告を受け取る」非同期の体験を狙っています。

技術的には、駅データ.jpのCSV、OpenStreetMapの実線形、Wikipediaの沿線情報、OpenAIの日記生成、Cloudflare Workersの静的配信、GitHub Actionsの夜間バッチ、といった無料〜低コストの部品を組み合わせただけです。ただ、実際に組み立てると「駅の座標はあるが線路の形がない」「LLMが日記を途中でやめる」「PWAのService Workerが全ページを真っ白にする」といった、地味だが必ずつまずくポイントがいくつもありました。そのあたりも含めて、作った順番で書いていきます。

分身の旅日記とは

コンセプトはシンプルです。

  • 分身(名前・絵文字・性格を持ったキャラ)が、日本の実在の鉄道路線を1本旅する
  • 途中下車した駅では長めの日記、通過する駅では車窓からの短い一言を書く
  • 沿線の実在スポット(名物・史跡・河川・橋梁・廃線跡など)を固有名詞で織り込む
  • 毎朝1本、全ユーザーが同じ「便」を読む新聞連載のような形で配信する
  • 旅した路線は日本地図に蓄積され、どこまで旅したかが一目で分かる

ポイントは「実在」であることです。架空の路線ではなく、駅の座標も線路の形も実データを使う。日記に出てくる橋や川や名物も、Wikipediaに載っている本物を使う。この具体性があると、単なる自動生成テキストが「どこか本当にありそうな旅」に見えてきます。

同じ路線でも、性格の違う分身が旅すると「何に目を留めるか」が変わって別の旅になります。廃線と橋梁が好きな無口な猫(絵文字は🐈‍⬛)と、食べ物と古い街並みに弱いのんびり屋では、同じ駅を通っても書く内容がまるで違う。ここが生成AIらしい面白さでした。

実際の画面はこんな見た目です。左に地図と分身のマーカー、右に日記のフィードが並び、「旅を再生」すると分身が線形の上を動きながら、日記が時系列で現れます。カードをクリックすると地図がその地点にジャンプします。

全体像: 認証もDBもない静的サイト

v1では、あえて認証もデータベースも持たせませんでした。全員が同じ便を読むので、ユーザーごとの状態が要らない。だから構成は次のように振り切れます。

  • 配信: trips/<日付>.json を静的ファイルとして置くだけ
  • 生成: 夜間バッチを1本回して、翌朝の便のJSONを作る

データの流れはこうです。

  1. 路線を選ぶ(日付をシードに決定的に選定)
  2. その路線の実線形をOpenStreetMapから取得(路線ごとに一度取ればキャッシュできる)
  3. 沿線スポットをWikipediaから集める
  4. ペルソナ + 路線 + スポットをプロンプトに渡してOpenAIで日記を生成
  5. trip JSONに保存して静的配信

配信はCloudflare Workers(静的アセット)、夜間バッチはGitHub Actionsのcronにしました。どちらも無料枠で収まります。ランニングコストで意味があるのは、実質OpenAIのAPI呼び出し(1便あたり1回)だけです。

駅はあるが線路がない: 実線形をどう取るか

最初のハマりどころがここでした。駅データ.jpの無料CSVには全国約1万駅の座標が入っていて、路線ごとに駅を並べることはできます。ただし、駅と駅を結ぶ「線路の形」は入っていません。駅を直線でつなぐと、カーブの多いローカル線はまるで別物になってしまいます。

そこでOpenStreetMapのOverpass APIから実際の線路の形を取ります。路線名で問い合わせるのですが、これが素直にいきません。

  • route relationがある路線とない路線がある(名松線にはなかった)
  • OSM上の路線名は表記ゆれがある(「南海電気鉄道加太線」のように事業者名付きだったり)
  • 他の路線と線路を共有する区間がある(加太線の和歌山市〜紀ノ川は南海本線を走る)

なので、relationを試してダメならway検索にフォールバックし、路線名は正規表現の部分一致で拾います。

[out:json][timeout:60];
relation["route"="train"]["name"~"加太線"];
out geom;

共用区間が厄介でした。路線名だけで引くと、共有している区間の線路が欠けて駅がつながりません。ここは、取得したway群の全頂点をノードとするグラフを作り、起点駅から終点駅までをダイクストラ法で最短経路探索する方式にしました。bbox指定で周辺の全路線の線路を集め、最大連結成分の中で起点・終点に最も近いノードを選んでから探索します。

取れた線形は、そのままだと点が多すぎるのでDouglas-Peuckerで200点前後に間引きます。環状線は始点=終点で退化するので、2分割してから間引く必要がありました。

正しく取れたかは、生成AIには任せず機械的に検証します。

  • 各駅を線形上に垂線射影して弧長を求め、駅の並び順で弧長が単調増加すること
  • 駅と線形の距離が数十m以内であること
  • 総延長が実路線長とほぼ一致すること

この3点で自動判定させました。実際に加太線を通したところ、共用区間を含めて12.1kmが得られ、実路線の12.0kmとほぼ一致しました。山手線34.5km、名松線43.5kmも実測とほぼ合致。線形の検証を機械化しておくと、路線を増やしたときに「なんか変な形になっている」を人手で見なくて済みます。

新幹線・地下鉄・ケーブルカーなどは旅の対象から外し、最終的に旅できる路線は537本になりました。

沿線スポットを集めて日記を書かせる

線形が取れたら、次は日記の材料です。ここの質で日記の面白さが決まります。定型文っぽくなると一気に冷めるので、実在の固有名詞をどれだけ織り込めるかが勝負でした。

材料はWikipediaのMediaWiki APIから、路線記事と各駅記事のイントロを抜き出して集めます。これをペルソナ定義(名前・性格・興味の対象・文体)と一緒にプロンプトへ渡し、OpenAIに日記の配列を書かせます。

出力の形は、構造化出力(response_formatjson_schema strictモード)で固定しました。各エントリは時刻・駅インデックス・種別(stop/pass)・本文HTMLという形です。

const body = {
  model: "gpt-4o-mini",
  messages: [
    { role: "system", content: system },
    { role: "user", content: user },
  ],
  response_format: {
    type: "json_schema",
    json_schema: { name: "diary", strict: true, schema: DIARY_SCHEMA },
  },
  max_completion_tokens: 12000,
};

これで、パースに失敗しない安定したJSONが返ってきます。固有名詞にはWikipediaや検索へのaタグを本文に埋め込ませ、「事実の捏造をしない。自信がないスポットは書かない」というルールもプロンプトで縛りました。

日記が途中の駅で止まる

公開後、7駅ある路線なのに日記が2駅目で終わっている便を見つけました。終点まで旅せずに、途中でぷつっと切れている。gpt-4o-miniが「全駅をたどって終点まで書く」という指示を守り切れず、短い出力で満足してしまったのが原因でした。

ここが生成AIを組み込むときの典型的なハマりどころだと思います。プロンプトで指示しても、モデルが弱いと平気で途中でやめる。そこで、プロンプトを強くするだけでなく、生成結果を検証して未達ならリトライする仕組みを足しました。

function isComplete(diary, stationCount) {
  if (!diary || diary.length < Math.max(3, Math.ceil(stationCount * 0.6))) return false;
  const maxSt = Math.max(...diary.map((e) => e.st));
  if (maxSt < stationCount - 1) return false; // 終点に到達していない
  if (!diary.some((e) => e.st === 0)) return false; // 起点がない
  return true;
}

終点に到達しているか、駅数に対して十分な件数か、起点があるかを見て、満たさなければ最大3回まで、より強い指示を添えて作り直します。プロンプトだけに頼らず、出力を機械で検証して駄目なら回す。この一手間で、モデルの気まぐれに振り回されにくくなりました。

品質をもう一段上げたいときは、OPENAI_MODELgpt-4o に切り替えれば指示追従が明確に良くなります。ここはコストとの相談で、環境変数一つで動かせるようにしてあります。

SNSで映えるOGP画像を動的に作る

拡散はSNS共有が頼りなので、リンクを貼ったときのカードにこだわりました。便ごとに、その路線の実際の形をそのまま絵にしたOGP画像(1200×630のPNG)を生成しています。

ここで二つ、地味だが重要な壁がありました。

一つ目は、SNSのクローラはJavaScriptを実行せずにHTMLの <head> しか読まない、という点です。SPAのままだと全ページ同じOGPになってしまう。なので、ビルド時に便ごとの実HTMLを生成し、og:titleog:image を焼き込んでおく必要があります。

二つ目は、画像内に日本語を描くために日本語フォントが要る、という点です。PNG化には @resvg/resvg-wasm を使いました。ネイティブ依存がなく、Cloudflareのビルド環境でもそのまま動くのが決め手です。

import { Resvg, initWasm } from "@resvg/resvg-wasm";

await initWasm(readFileSync("node_modules/@resvg/resvg-wasm/index_bg.wasm"));
const r = new Resvg(svg, {
  font: { fontBuffers: [fontBuffer], defaultFontFamily: "Noto Sans JP", loadSystemFonts: false },
  fitTo: { mode: "width", value: 1200 },
});
writeFileSync(out, r.render().asPng());

ここでもう一つつまずきました。Noto Sans JPのバリアブルフォントをそのまま渡すと、SVGで font-weight: 700 を指定しても細いウェイトで描画されてしまう。文字が細くて薄い、と指摘を受けて気づきました。resvgがバリアブルフォントのウェイト軸を効かせてくれないようです。

対処は、fonttoolsでバリアブルフォントを太字ウェイトに固定した静的インスタンスを作り、それを渡すことでした。

python3 -m fontTools.varLib.instancer "NotoSansJP[wght].ttf" wght=700 -o NotoSansJP-Bold.ttf

これで路線名も駅名もしっかり太字で描画されるようになりました。実際に生成されるカードはこんな見た目です。山手線ならあのループの形が、そのまま画像になります。

生成されたOGPカード。路線の実際の形と、路線名・区間・距離が入っている

画像生成は依存インストールが絡んで壊れやすいので、動的importとtry/catchで囲み、失敗してもサイトのビルド自体は止まらないようにしてあります。

全ページが真っ白: Service Workerの罠

公開して少し経ってから、「他の旅を選んでも表示されない」「リンクを開くとERR_FAILED」という報告が来ました。トップから個別の旅に遷移すると、ページが真っ白になる。

原因はPWA用に入れていたService Workerでした。ナビゲーション時にキャッシュした /index.html を返す実装だったのですが、Cloudflareの静的アセット配信では /index.html がリダイレクト扱いになるため、それをキャッシュから返すと空のページになってしまう。初回の直リンクだけはService Workerが制御する前なので動き、以降のナビゲーションが壊れる、という分かりにくい壊れ方でした。

ここは判断として、v1ではオフライン機能よりも確実性を優先し、Service Workerを廃止しました。ただ、すでに壊れたSWを持ってしまったブラウザを放置できないので、新しいSWを「キルスイッチ」にして、起動時に全キャッシュを削除して自身をunregisterし、開いているタブを再読込させるようにしました。

self.addEventListener("activate", (event) => {
  event.waitUntil((async () => {
    const keys = await caches.keys();
    await Promise.all(keys.map((k) => caches.delete(k)));
    await self.registration.unregister();
    const clients = await self.clients.matchAll({ type: "window" });
    for (const client of clients) client.navigate(client.url);
  })());
});

これをデプロイすると、壊れたSWを持つブラウザも次のアクセスで自動的に回復しました。よかれと思って入れたキャッシュ機構が体験を壊す、というのはPWAでありがちな事故だと思います。オフライン対応が必須でないなら、無理に入れない方が安全でした。

トップは「日本が埋まっていく」地図に

便が増えていく蓄積型のサービスなので、トップページには全国カバレッジマップを置きました。これまで旅した全路線を日本地図にプロットし、便が増えるほど日本が線で埋まっていくのが見えるようにしています。

最初は路線の線を赤系にしていたのですが、OpenStreetMapの地図は道路も鉄道も赤やピンクなので紛れてしまう。濃い紫にして、さらに白いふち(ケーシング)を敷くことで、背景に関係なく浮いて見えるようにしました。地図上に自前の線を重ねるときの定番テクニックです。

また、地図と統計だけだと「これが何のサイトか」が初見では伝わらないので、コンセプトの説明文と、その日の日記の冒頭を引用する「今朝の一節」を足しました。読む前の味見があると、地図だけでは出せない読み物としての引力が出ます。

公開: Cloudflare Workers + 独自ドメイン

配信はCloudflare Workersの静的アセットにしました。wrangler.jsoncnot_found_handlingsingle-page-application にしておくと、実ファイルのないパスには index.html を返してくれます。便ごとの実HTMLは実ファイルとして置いてあるので、そちらが優先され、OGPメタも正しく配信されます。

GitHub Actionsの夜間バッチは、毎日日本時間の朝4時に翌朝の便を生成してコミットし、pushをトリガーにCloudflareが自動で再デプロイします。生成に必要なOpenAIキーはリポジトリのSecretに置くので、手元に鍵がなくても運用が回ります。

最後に独自ドメイン bunshin-tabi.com を取りました。OGP画像や共有リンクのURLは環境変数 SITE_URL 一つで切り替わるようにしてあったので、ドメインを繋いでからその値を差し替えるだけで、カードもリンクも一斉に新ドメインへ移りました。

法務面も先に確認しています。駅データ.jpは商用利用・加工利用ともにOK、OpenStreetMapはODbLで「© OpenStreetMap contributors」の表記を維持、地理院タイルは出典表記を維持。これらはサイトのクレジット表記で明示しています。

まとめ

分身の旅日記をゼロから作って公開してみて分かったことをまとめます。

  • 駅の座標はオープンデータで手に入るが、線路の形はOSMから別途取る必要がある。共用区間はグラフ + ダイクストラで解くのが確実
  • 線形が正しいかは生成AIに判断させず、弧長の単調増加・駅との距離・総延長で機械的に検証すると、路線を増やしても破綻に気づける
  • LLMは指示しても途中で仕事をやめることがある。出力を検証して未達ならリトライする仕組みを一枚かませると安定する
  • OGPは、SNSクローラがJSを読まないので便ごとの実HTMLにメタを焼き込む必要がある。画像内の日本語はバリアブルフォントを静的ウェイトに固定してから描く
  • PWAのService Workerは、よかれと思って入れると全ページを壊すことがある。オフラインが必須でないなら無理に入れない
  • 静的配信 + 夜間バッチ + LLM 1コールという構成なら、認証もDBもなしで、ほぼ無料枠で個人サービスとして公開まで持っていける

一番の収穫は、生成AIを組み込むほど「AIに任せない部分」の設計が効いてくる、と実感できたことでした。線形の検証も、日記の完全性チェックも、やっているのはただの機械的な判定です。生成の面白さは残しつつ、破綻しうるところは古典的なコードで押さえる。この役割分担がうまくいくと、毎朝ひとりでに旅が増えていくサービスが、手を離しても回るようになりました。

まだ数便しかありませんが、蓄積型のサービスなので本番は数週間後です。日本地図が線で埋まっていくのを、のんびり眺めようと思います。

参考リンク

単価比較アプリ「どっち安い?」を公開しました - 入れてから外した機能の記録

はじめに

iOSアプリ「どっち安い?」をApp Storeで公開しました。

どっち安い? - グラム単価計算

どっち安い? - グラム単価計算

  • icot
  • ショッピング
  • 無料
apps.apple.com

スーパーの棚の前で「500gで398円」と「1kgで680円」のどっちが安いかを、数タップで判定するアプリです。無料、広告なし、iOS 17.0以降。

機能を足していった記録ではなく、一度入れてから外したものと、中心機能を2回作り直した話を書きます。個人開発だと足す話ばかりが記事になりますが、実際に効いたのは削る判断のほうでした。

どっち安い? とは

やることは2つだけです。

価格と内容量を入れると、100gあたりの値段に揃えて比べます。安い方に印が付いて、何%お得かが出ます。g / kg / ml / L / 個 / 枚 / 回分に対応していて、「500g×3袋」のようなまとめ買いもそのまま計算できます。

もう1つが「いつもの単価」です。よく買うものは、そのときの値段と内容量を覚えておけます。次にその商品を選ぶと覚えた数字が入力欄に入るので、目の前の値段を打つだけで「いつもより安いか」が分かります。

技術構成はSwiftUI + SwiftData、プロジェクト生成はXcodeGenです。通信コードは1行もなく、データは端末内だけに保存しています。

入れてから外したもの

税抜/税込の切り替え

最初は税抜入力に対応していました。軽減税率8%と標準税率10%を選べて、1円未満を切り捨てて税込に直す実装です。テストも書きました。

外しました。理由は単純で、単価比較の答えを変えないからです。

同じカテゴリの2商品なら税率も同じです。両方に同じ率を掛けても「どっちが安いか」も「何%お得か」も変わりません。変わるのは絶対額の表示だけ。

しかも総額表示義務があるので、値札には必ず税込価格が載っています。税抜を大きく書く店でも税込は併記されていて、書き方は店内で揃っている。つまりあのトグルは「同じ値札に並んでいる2つの数字の、どっちを打つか」でしかありませんでした。

店頭で1タップでも減らしたいアプリで、答えに影響しないことをユーザーに考えさせていたわけです。

おまけに副作用もありました。「いつもの単価」は先月税抜で打って今月税込で打つと基準がずれます。入り口が1つなら起きない問題を、自分で作っていました。

そして8%/10%は、法改正でコードが間違いになる唯一の箇所でもありました。

ウィジェット3つ

ロック画面ランチャー、ホーム画面の小と中、合わせて3つ作りました。中サイズは「いつもの単価」を上位3件出して、行をタップするとその商品を選んだ状態で比較画面が開く、という作りです。

これも外しました。きっかけは「周回の買い物なのに、肉売り場に直行するの?」という指摘でした。

ウィジェットのタップが効くのは、ポケットから出す前に「今から何を見るか」が決まっているときだけです。でも実際の買い物は店内を回るもので、目の前に商品が現れてから「これ安い?」と思う。順番が逆でした。

中サイズに出るのは上位3件なので、メモが5件あれば目当てが載っている保証もありません。結局アプリを開いて選び直すことになります。

外して分かったのは、コストのほうが大きかったことです。

  • ウィジェットのためにApp Groupが必要で、それが署名付きArchiveを止めていた唯一の原因だった
  • Xcode 26.6のシミュレータではウィジェットギャラリーが全アプリで空になる環境問題があり、一度も描画を確認できていなかった

つまり、未検証のコードのために提出がブロックされていました。削除したら署名付きArchiveがそのまま通るようになりました。

"SigningIdentity" => "Apple Development: Takaaki Yayoi (N925JXBD3G)"

2回やり直した「いつもの単価」

このアプリの差別化はここだけなので、いちばん時間をかけました。そして2回やらかしました。どちらも原因は同じで、枠が2種類になっていたことです。

1回目: 1つの枠に2つの意味を入れた

最初の実装は、メモを枠に「貼り付ける」形でした。1枠目にメモを結びつけて、同じ1枠目に目の前の商品の値段も打つ。単価の下に「いつもより12%安い」と出す作りです。

実機で使ってみると、どこに何を打つのか分かりませんでした。1つの枠に「覚えている商品」と「目の前の商品」が同居していたからです。

さらに悪いことに、画面上部のバナーは「もう1つ入れると比べられます」と表示していました。メモとの比較はもう成立しているのに、いちばん目立つ場所が「まだ比べられない」と言っている。矛盾していました。

2回目: 入力欄のない枠を作った

次に考えたのが「メモが枠を占める」案です。1枠目がメモそのものになって、2枠目に目の前の商品を打つ。

これも却下されました。「入力欄のない枠を作ったら、結局それも普通の枠とは別物じゃないか」と。そのとおりです。見た目も振る舞いも違う枠が2種類できるだけで、1回目と同じ問題でした。

3回目: 枠は1種類、メモは数字を入れるだけ

最終的にこうなりました。

メモを選ぶと、その枠の入力欄に前回の数字がそのまま入る。それだけ。

① [にく] 398円 500g g ×1   ← メモを選ぶと数字が入る。普通の枠
②     450円 500g g ×1   ← 目の前の商品を打つ
        ↓
   1つ目が安い / 12%お得

枠は1種類しかありません。メモは「入力を代わりに打ってくれるショートカット」であって、それ以上の意味を持たせない。あとは普通の枠が2つ並んでいるだけなので、普通の最安判定がそのまま答えになります

この形にした結果、消えたものがあります。

  • メモ専用のバナー表示
  • カード内の「いつもより○%安い」判定行と、そのための判定ロジック一式

「1つ目が安い / 12%お得」が同じことを言っているので、二重でした。設計が正しくなると、書いたコードが減ります。

データモデルも変わりました。当初は正規化済みの単価(100gあたり79.6円)を保存していましたが、値札の数字(価格・内容量・単位・個数)を保存して単価は毎回計算する形に変えました。副作用として、メモ一覧が「398円 / 500g → 100gあたり」と覚えている中身をそのまま見せられるようになり、編集も単価ではなく値段を直す形になって打ちやすくなりました。

UIで踏んだ失敗

押した先が行き止まりだった

枠にはメモ呼び出し用のボタンがあります。メモが1件もない状態でこれを押すと、「メモがまだありません」という押せない文字が1行出るだけでした。

そこから何をすればいいのか、どこにも書いていません。完全な行き止まりです。

直したあとは、メモが無くても必ず次の行動が1つは出るようにしました。価格が入っていれば「この単価を覚える…」、何も入っていなければ「価格と内容量を入れると、この商品を覚えられます」。

ついでに、この修正で元の設計の穴にも気づきました。「覚える」ボタンは画面下に1つしかなく、いちばん安い枠しか覚えられなかったのです。2つ目の商品を覚えたくてもできませんでした。

アイコンだけのボタンを作った

メモの呼び出し口は、薄いグレーのブックマークアイコンでした。ラベルなし、メニューが開くことを示す記号もなし。

「ここから呼び出せるとは分からない」と指摘されました。そのとおりで、アプリの差別化機能の入口を、いちばん伝わらない形で置いていました。

文字を付けて解決しました。

  • 未選択: 🔖 いつもの ⌄
  • 選択済み: 🍖 鶏むね肉 ⌄

スクロールする中身にボタンを置いた

「覚える」ボタンをスクロール領域の中に置いていました。中身の量で位置が動くので、条件が揃うと画面外に出ます。

これは2回直しました。1回目は「案内文を2行から1行にする」で対処したのですが、不十分でした。

原因は検証の甘さです。確認したのが1枠だけ入力した状態で、結果バナーが低いときだったのです。バナーは状態で高さが変わります。

状態 バナーの高さ
未入力 低い
1枠だけ
結果あり 高い

実際にいちばんよく見る「結果あり」の状態を見ていませんでした。

2回目は構造から直しました。操作するものは固定、読むものはスクロール。「覚える」「枠を追加」をテンキーと同じ固定領域に移したので、枠を4つに増やしてもバナーが伸びても影響を受けません。

再発防止に、枠4つの最悪ケースを起動引数だけで再現できるようにしました。

xcrun simctl launch $D com.icot.unitprice -screenshot-seed -screenshot-scene crowded

BUILD SUCCEEDED は変更が入った証拠にならない

これが今回いちばん時間を溶かしました。

呼び出しチップに文字を足したのに、シミュレータで見ると見た目が変わりません。ソースを何度読んでも修正は入っています。ビルドは成功しています。インストールし直しても変わりません。

「レイアウトが崩れて文字が潰れているのでは」と考えて、ヘッダーの横幅を計算したり、layoutPriority を検討したり、しばらく別方向を掘りました。

原因は単純で、xcodebuild がそのファイルを再コンパイルしていなかっただけでした。touch して再ビルドしたら、すんなり反映されました。

$ xcodebuild ... build | grep -E "SlotCardView|\*\*"
** BUILD SUCCEEDED **          # ← コンパイル行が無い

$ touch UnitPrice/Views/SlotCardView.swift
$ xcodebuild ... build | grep -E "SlotCardView|\*\*"
SwiftCompile normal arm64 Compiling\ SlotCardView.swift ...   # ← 今度は走った
** BUILD SUCCEEDED **

** BUILD SUCCEEDED ** は「ビルドが成功した」としか言っていません。自分の変更が入ったかどうかは何も保証していない。

UIを直したのに見た目が変わらないときは、レイアウトを疑う前にビルドログに SwiftCompile 対象ファイル名 が出ているかを見る。これを飛ばしたせいで、存在しないレイアウトバグを何往復も追いかけました。

まとめ

公開までに分かったことをまとめます。

  • 単価比較では税率が答えを変えない。同じ率が両方に掛かるので、順位も差率も不変。変わるのは絶対額の表示だけ
  • ウィジェットは、ポケットから出す前に何を見るか決まっているときしか効かない。周回の買い物では順番が逆
  • 中心機能は2回作り直した。原因はどちらも枠が2種類あること。1種類に統一したら、専用の判定表示もロジックも丸ごと不要になった
  • 導出値を保存しない。単価ではなく値札の数字を保存して毎回計算する形にしたら、表示も編集も素直になった
  • アイコンだけのボタンは、何ができるか伝わらない。押した先が行き止まりのメニューも作らない
  • 操作するものは固定、読むものはスクロール。レイアウトの確認は「いちばん背が高くなる状態」でやる
  • 「BUILD SUCCEEDED」は変更が入った証拠にならない。ビルドログにコンパイル行が出ているかを見る

一番の収穫は、指摘を受けて削った判断がどれも正解だったことでした。税率もウィジェットも、自分では「せっかく作ったから」と残す方向に理屈をこねていました。作った本人は足したものを守りたくなるので、「これ要るの?」と聞いてくれる相手がいるかどうかが、個人開発では効くのだと思います。

削った結果、提出をブロックしていた要因も消えました。皮肉ですが、いちばん厄介だった問題は、いちばん要らなかった機能が持ち込んでいたものでした。

参考リンク

「いつぶり?」を公開しました — カレンダーに書くほどでもないことの、前回からの日数だけを数えるアプリ

iOS アプリ「いつぶり? 掃除と手入れの記録」を公開しました。

シーツを洗ったのはいつだったか。換気扇のフィルターを替えたのはいつか。カレンダーに書くほどでもないけれど、ふと気になる。そういうことの前回からの日数だけを、そっと数えておくアプリです。

いつぶり? 掃除と手入れの記録

いつぶり? 掃除と手入れの記録

  • icot
  • ライフスタイル
  • 無料
apps.apple.com

やることリストを作りたかったわけではない

ToDo アプリも習慣トラッカーも既にたくさんあります。作りたかったのはそれではありませんでした。

「シーツを洗う」を ToDo に入れると、期限が来て、できないと赤くなって、未達成のまま溜まっていきます。でも実際のところシーツ洗濯に締切はありません。 12日前でも18日前でも困らない。ただ「そういえば、いつぶりだろう」と思い出せさえすればいい。

なので、このアプリには完了・未完了という状態がありません。期限もありません。通知も出しません。できなかった日を責める仕組みもありません。前にやった日からの日数が、ただ静かに増えていくだけです。

主役はウィジェット

アプリを開いて記録するのでは、たぶん続きません。ホーム画面から直接押せることが、このアプリの本体だと考えました。

中サイズ — そろそろのもの4つと「やった!」ボタン

そろそろのものを4件並べて、各行に「やった!」ボタンを置いています。押した瞬間にその場で「今日」に変わります。アプリは開きません。

押し間違えたときは、薄くなった✓をもう一度押せば取り消せます。消えるのはその日の1件だけで、過去の記録には触りません。

小サイズ / ロック画面

選んだひとつを大きく出します。ロック画面には「🐕 ペットのシャンプー / 28日前」のように出ます。

そろそろ度を「棒グラフ」で表すのをやめた話

目安の間隔を決めておくと、そろそろ具合が分かるようにしています。最初はプログレスバーで作りました。実際に使ってみて、これは失敗でした。

一覧に並べたときにこうなったんです。

経過 目安 バーの長さ
ペットのシャンプー 28日 30日 6割
猫のトイレ砂 26日 14日 満タン

日数が多いほうがバーが短い。 そろそろ度は「そのアイテムの目安に対する割合」なので計算は正しいのですが、縦に並べると人は長さを横断的に比べてしまいます。バーの長さは同じ行の中でしか意味を持たないのに、リストは比較を誘う形をしています。

もうひとつ、進捗バーは普通「達成」を意味します。満タン=やり遂げた、という学習済みの読み方がある。でもこのアプリで満タンは放置が進んだ状態で、意味が逆でした。

そこで、長さをやめて小さな点の色にしました。緑から黄、オレンジへ。赤には行きません。「期限切れ」ではなく「そろそろ」で止めたいからです。

色だけに頼らないよう、点は大きさも少しずつ変えています。色の見分けがつきにくい人にも同じ情報が伝わるように、そろそろ度が上がると点も少しだけ大きくなります。

舞台裏

状態を保存せず、常に計算する

経過日数・平均間隔・そろそろ度はどこにも保存していません。毎回 Log(実施記録)から計算しています。

これは以前作った別のアプリで踏んだ地雷の反省です。「保存時点の状態」をウィジェットに焼き込むと、日をまたいだときに古い値が出続けます。

ウィジェットのタイムラインも同じ考え方で作りました。エントリが運ぶのは「最終実施日」という事実だけで、日数は持ちません。日付の変わり目にエントリを置いておき、描画時にそのエントリの日付から計算します。

// エントリは日数を持たない。lastDoneAt という事実だけを運ぶ
struct ItemSnapshot {
    let lastDoneAt: Date?
    let intervalDays: Int?

    func days(asOf reference: Date) -> Int? { ... }
}

同じスナップショットでも、エントリの日付が進めば 12日前 → 13日前 → 14日前 と変わります。ユニットテストで押さえてあります。

経過日数は「時刻の差」ではなくカレンダー日の差で数えています。前日23時にやって当日1時に見たら、2時間しか経っていなくても「1日前」です。

ウィジェットとのデータ共有

SwiftData のストアを App Group のコンテナに置いて、ウィジェット拡張から同じファイルを読んでいます。ウィジェットで押した記録がアプリ側にも反映されます。

ボタンは App Intents です。Button(intent:) で置くだけで、拡張プロセスの中で記録が走り、タイムラインが更新されます。

ハマったところ

ウィジェットのボタンが青くなる。 Color.accentColor はウィジェット拡張ではアセットカタログを読まず、システム既定の青にフォールバックします。拡張の Assets.car に色はちゃんと入っていたので、色が無いのではなく accentColor が見ていないだけでした。Color("AccentColor", bundle: .main) と名前で明示して解決。Bundle.main は拡張の中では拡張自身を指します。

シミュレータのウィジェットギャラリーが空になる。 Xcode 26.6 のシミュレータで、Apple 製を含む全アプリのウィジェットがギャラリーに出てこない現象に当たりました。別のシミュレータでも再起動でも直らず。結局ウィジェットの確認は実機でやりました。ここで時間を溶かさないよう、リポジトリのメモに残してあります。

プリセット名の重複判定。 プリセットは日英で翻訳されるので、表示名で「追加済み」を判定すると言語を切り替えた瞬間に壊れます。言語非依存のキーを持たせて判定するようにしました。

選ぶだけで始められます

シーツ洗濯、枕カバー交換、換気扇フィルター、冷蔵庫の掃除、歯ブラシ交換、爪切り、散髪、車のオイル交換、タイヤの空気圧、防災用品の点検、火災報知器の点検、観葉植物の水やり、ペットの爪切り、猫のトイレ砂の総取り替え……

日本の暮らしに合わせたプリセットを41種類用意しました。自由入力もできます。絵文字も好きなものを選べるので、アイコン素材を作らなくて済むのが個人開発には楽です。

目安の間隔は決めなくても構いません。決めなければ点が出ないだけで、日数はちゃんと数えています。

プライバシー

記録はすべて端末の中だけに保存されます。ネットワーク通信は一切ありません(URLSession の類はコードに1行も入っていません)。広告なし、アクセス解析なし、アカウント登録なし。

これから

しばらく自分で使ってみて、足りないものがあれば足していきます。いまのところ通知を入れる予定はありません。通知が来た時点で、このアプリが避けたかったものになってしまうので。

よければ使ってみてください。

定時タイマー v1.7.0 — 退勤までの残り時間をロック画面に常駐させた

はじめに

自作の iOS アプリ「定時タイマー」の v1.7.0 をリリースしました。定時までの残り時間をカウントダウンするだけのアプリです。

なおリリース後に、小サイズのウィジェットが更新されなくなる不具合が見つかり、v1.7.1 で修正しています。原因が面白かったので、記事の後半で書きます。

これまでは通知でお知らせするだけでしたが、今回のアップデートで Live Activity に対応し、ロック画面と Dynamic Island に残り時間が常駐するようになりました。コンセプトは「アプリを開かなくても、退勤までの残り時間が常に視界にある」ことです。

通知は届いた瞬間しか見えません。気づいたときには通知センターの奥に埋もれていて、結局あと何時間なのかは分からない。一方 Live Activity は、ロックを解除するたびに必ず目に入ります。この差が、今回いちばん作りたかったところです。

何ができるようになったかを順に書いていきます。

退勤Live Activity: ロック画面とDynamic Islandに常駐する

始業時刻に通知が届き、そこからアプリを開くと、ロック画面に定時までの残り時間と進捗バーが出ます。あとはその日ずっと残り続けます。

一度出てしまえば、残り時間は秒単位で動き続けます。アプリを起動しておく必要はありません。

なお ActivityKit の制約で、Live Activity はアプリがフォアグラウンドにあるときしか開始できません。始業時刻ちょうどに勝手に出す、というのは作れないので、朝いちど通知から開いてもらう流れになっています。アプリ起動なしで開始したい場合は iOS 17.2 以降の push-to-start が必要で、プッシュを送るサーバーが要ります。

Dynamic Island にも同じ情報が出ます。普段は残り時間だけのコンパクト表示で、長押しすると進捗バーと操作ボタンまで開きます。

表示は時間帯によって変わります。

  • 勤務中は青
  • 終業1時間前になるとオレンジ
  • 定時になると「定時です!」
  • 過ぎたら赤で超過時間をカウントアップ

自分で作っておいて言うのもなんですが、赤い数字が増えていくのは思ったより効きます。「まだ大丈夫」と思っているときほど、視界の端で赤が動いているのが気になります。

休日には表示されません。設定で選んだ休みの曜日は、通知も Live Activity も出ないようにしてあります。

設定画面のLive Activityセクション。退勤カウントダウンを表示のトグルと、休みの曜日の選択

ワンタップ操作: ロック画面から今日を終わらせる

Live Activity には3つのボタンを載せました。ロック画面からも、アプリからも同じ操作ができます。

今日はここまで

今日の勤務を終了します。カウントダウンが止まり、その日の残りの通知もまとめて止まります。区切りをつける行為そのものが、切り上げの合図になる。そういう狙いのボタンです。

押し間違えても、アプリから「終了を取り消す」で戻せます。

延長 +15/30/60分

もう少し続けるときに終業時刻を後ろにずらします。設定そのものは変えず、その日だけの調整として扱います。翌日には元の勤務時間に戻ります。

休憩中

席を外すあいだ、カウントダウンを止めます。休憩した分だけ終業時刻が後ろにずれるので、残りの勤務時間は保たれます。

ホーム画面では、これらの操作が残り時間の下に並びます。

退勤ログと定時帰宅ストリーク

定時で帰れた日と超過した日を、自動で記録するようにしました。

「今日はここまで」を押した時刻がそのまま記録されます。押さなかった日は、終業予定時刻に帰ったものとして翌日に確定します。定時から5分以内の超過は定時扱いです。

連続で定時帰宅できた日数は、ストリークとしてホーム画面とログ画面に出ます。休日はカウントを飛ばすので、週末をはさんでも途切れません。

記録の方針で一点だけ迷いました。アプリを使っていない日を勝手に「定時帰宅」として埋めれば、ストリークは簡単に伸びます。ただ、それは実態のない数字です。アプリが動いていた勤務日だけを記録対象にして、使っていない日はストリークをそこで止めるようにしました。伸びにくくなりますが、数字の意味は残ります。

グラフや CSV 書き出しは今回は入れていません。まずは「定時で帰れたかどうか」だけを積み上げるところからにしました。

StandBy対応: 卓上の退勤時計になる

横向きに置いて充電しているあいだは、StandBy の卓上時計として残り時間を大きく表示します。

会議中や作業中に机の上に置いておくと、手を伸ばさなくても残りが見えます。常時表示のときは配色を落として、暗い部屋でもまぶしくならないようにしてあります。

3画面構成への整理と不具合修正

設定がシート表示だったものを、ホーム / ログ / 設定の3画面構成に整理しました。ログ画面が増えたぶん、設定をタブに移した形です。

不具合も2つ直しています。

  • 中サイズウィジェットが、小サイズのレイアウトで表示されていた
  • ウィジェットと Watch のコンプリケーションが、平日になっても休日表示のままになることがあった

後者は、休日判定を「保存した時点の値」で持っていたのが原因でした。土曜日に保存されたデータが月曜日になっても「おやすみ」のままになる。曜日の集合を持たせて、表示する日付ごとに判定するように変えました。

実装で気をつけたこと

機能の話が中心の記事ですが、Live Activity まわりで判断が要ったところを2つだけ残しておきます。

時間による表示の切り替えはOSに任せられる部分と、そうでない部分がある

ActivityKit には WidgetKit のようなタイムラインがありません。未来のエントリを先に積んでおく仕組みがなく、再描画はアプリからの更新かプッシュ通知でしか起きません。

ただし、時間の経過そのものは OS が描画してくれます。

Text(
    timerInterval: state.startDate...state.endDate,
    pauseTime: state.breakStartedAt,
    countsDown: true
)

これでアプリが動いていなくても秒単位でカウントダウンします。pauseTime に日付を渡すとそこで止まるので、「休憩中」の一時停止もこれで表現できます。進捗バーも ProgressView(timerInterval:) で同じように動きます。

一方、色とラベルは描画時に確定するので自動では変わりません。ここは staleDate に次の切り替え時刻を渡し、あわせてバックグラウンド更新を予約する形で寄せました。OS の裁量が入るので、秒単位の正確さは出ません。プッシュを使わない構成での割り切りどころです。

ロック画面のボタンはLiveActivityIntentにする

Live Activity 上のボタンは App Intents で動きますが、AppIntent ではなく LiveActivityIntent を使いました。

struct EndWorkIntent: LiveActivityIntent {
    @MainActor
    func perform() async throws -> some IntentResult {
        await WorkController.shared.endWork()
        return .result()
    }
}

LiveActivityIntent はアプリのプロセスで実行されます。「今日はここまで」でやりたいのは Live Activity を畳むことだけではなく、その日の通知を止めることでもあるので、アプリ側の処理をそのまま呼べるこちらが都合よく収まりました。

リリース後: 小サイズのウィジェットだけ更新されなくなっていた

v1.7.0 を出したあと、自分の端末で妙な状態に気づきました。小サイズのウィジェットが「おやすみ」表示のまま、平日になっても戻らない。中サイズは正常。ウィジェットを置き直しても、端末を再起動しても、修正版をビルドして入れても変わりません。

原因にたどり着くまでにだいぶ遠回りしたので、結論から書きます。ウィジェットに置いていたロゴ画像が 1024x1024 で、小サイズのアーカイブ上限を超えていました。

デバイスのコンソールログにそのまま出ていました。

Widget archival failed due to image being too large [1]
- (1024, 1024), totalArea: 1048576 > max[1001127.600000]

Request ended for TeijiTimerWidget:systemSmall - error: imageTooLarge
Request ended for TeijiTimerWidget:systemMedium - success

WidgetKit はタイムラインを画像としてアーカイブしますが、そこに載せられる画像の面積に上限があります。この上限はウィジェットの表示面積に比例するため、中サイズは通って小サイズだけ失敗していました。

やっかいなのはここからです。

失敗すると、最後に成功した描画が残り続ける

アーカイブに失敗したとき、iOS はエラーを出す代わりに、最後に成功した描画をそのまま出し続けます。

そして休日表示のブランチは SF Symbol しか使っていません。

if entry.data.isDayOff(at: entry.date) {
    Image(systemName: "moon.zzz.fill")   // ← ロゴを使わないので成功する
    Text(NSLocalizedString("widget.dayoff", comment: ""))
} else {
    Image("AppLogo")                     // ← 1024x1024。ここで必ず失敗する
    ...
}

つまり「おやすみ」は保存でき、タイマー表示は保存できない。一度休日に描画されると、以降そこから抜け出せなくなります。ビルドしても置き直しても再起動しても変わらないのは、画像の大きさが変わっていないからでした。

画面の小さい端末でしか起きない

上限が表示面積に比例するので、大きい端末では再現しません。私は iPhone 17 Pro Max のシミュレータで確認していて、そこでは正常に表示されていました。実機は画面幅 375pt でウィジェットが 159x159。ここでぎりぎり足りていませんでした。

シミュレータで再現しないぶん、手元の環境だけで確認していると気づけません。ウィジェットを実装したら、小さい端末でも一度見ておいたほうがよさそうです。

表示サイズではなく元画像のサイズで決まる

このロゴは 20x20 で表示していました。

Image("AppLogo")
    .resizable()
    .scaledToFit()
    .frame(width: 20, height: 20)

小さく表示していても、アーカイブされるのは元の解像度のままです。.frame() で縮めても意味がありません。アセット側を 96x96 に差し替えて解決しました。3x でも 72px あれば足りる用途なので、1024 は完全に無駄でした。

App Icon 用の 1024x1024 をそのままウィジェットのアセットにも入れていた、というのが元をたどればすべての始まりです。

まとめ

定時タイマー v1.7.0 で入れたものをまとめます。

  • 退勤 Live Activity。ロック画面と Dynamic Island に残り時間と進捗が常駐する
  • 終業1時間前で色が変わり、定時で「定時です!」、超過は赤でカウントアップ
  • 「今日はここまで」「延長 +15/30/60分」「休憩中」をロック画面から直接操作できる
  • 「今日はここまで」を押すと、その日の通知とカウントダウンがまとめて止まる
  • 定時で帰れた日を自動記録し、定時帰宅ストリークを表示。休日はカウントを飛ばす
  • StandBy 対応。横向き充電中は卓上の退勤時計になる
  • ホーム / ログ / 設定の3画面構成に整理
  • 中サイズウィジェットのレイアウトと、休日表示が残る不具合を修正
  • ウィジェットに置く画像は、表示サイズではなく元画像のサイズでアーカイブ上限に引っかかる。上限は表示面積に比例するので、小さい端末の小さいウィジェットが最初に壊れる
  • アーカイブに失敗したウィジェットはエラーを出さず、最後に成功した描画を出し続ける

一番作ってよかったのは、やはりロック画面の常駐表示でした。通知は「見逃せるもの」ですが、ロック画面は一日に何十回も見ます。同じ情報でも、どこに置くかで届き方がまるで変わる。機能を足すより置き場所を変える方が効くことがある、というのが今回の実感です。

一方で一番反省したのは、リリース後のウィジェットの件でした。症状が説明できないまま「キャッシュだろう」と決めつけて、ビルドと確認を何往復もさせてしまいました。デバイスのログには最初から答えが書いてあったので、辻褄が合わなくなった時点でログを見にいくべきでした。手元の端末で再現しない不具合ほど、推測ではなくログから入るのが結局は早い、というのが今回の教訓です。

参考リンク

どっち安い?プライバシーポリシー

「どっち安い?」(以下「本アプリ」)のプライバシーポリシーです。

はじめに

本アプリは、利用者の情報を一切収集しません。

本アプリには通信機能がありません。広告も、アクセス解析も、外部のSDKも組み込んでいません。入力した内容はすべて、お使いの端末の中だけに保存されます。

収集しない情報

本アプリは、以下をはじめとするいかなる情報も収集・送信しません。

  • 氏名、メールアドレス、電話番号などの個人を特定できる情報
  • 位置情報
  • 端末の識別子(IDFA、IDFV など)
  • 利用状況、操作ログ、クラッシュログ
  • 連絡先、写真、カメラ、マイクなどへのアクセス

本アプリは、これらの権限を一切要求しません。アカウント登録やログインもありません。

端末内に保存される情報

本アプリは、機能のために以下の情報をお使いの端末の中だけに保存します。これらが外部に送信されることはありません。

保存されるもの 内容
いつもの単価メモ 利用者が付けた商品名、目印の絵文字、価格、内容量、単位、個数、作成日と更新日
比較の履歴 比較した価格・内容量・単位・個数と、その日時(直近50件)
設定 既定の単位、案内表示を済ませたかどうか

これらは本アプリ専用の保存領域に置かれ、他のアプリから読み取られることはありません。

第三者への提供

収集していないため、第三者へ提供する情報はありません。情報を第三者に販売・共有・貸与することもありません。

バックアップについて

端末内に保存された上記の情報は、利用者ご自身が設定している iPhone のバックアップ(iCloud バックアップ、またはパソコンへのバックアップ)に含まれる場合があります。これは Apple が提供する端末の仕組みによるもので、本アプリが情報を送信しているものではありません。バックアップの設定は、iOS の「設定」から利用者ご自身で変更できます。

なお、本アプリは iCloud による端末間の同期を行いません。

外部リンクについて

本アプリの設定画面には、以下の外部リンクがあります。

  • プライバシーポリシー / サポート: ウェブブラウザで外部のページを開きます。開いた先のサイトには、そのサイト自身のプライバシーポリシーが適用されます。
  • 問い合わせ: メールアプリを開き、開発者の宛先を入力した状態にします。利用者が実際にメールを送信した場合に限り、開発者はそのメールアドレスと本文を受け取ります。受け取った内容は、問い合わせへの回答のためだけに利用し、他の目的には使用しません。

いずれも、利用者がリンクを開かない限り何も起こりません。

データの削除

  • いつもの単価メモ: アプリ内の「いつもの単価」一覧から、左スワイプで個別に削除できます。
  • 履歴: 履歴画面の「すべて消す」で一括削除できます。個別の削除は左スワイプで行えます。
  • すべてのデータ: 本アプリを端末から削除すると、保存されている情報はすべて消去されます。

お子様の利用について

本アプリは情報を収集しないため、年齢にかかわらず安心してご利用いただけます。保護者の同意を必要とする情報の取得も行いません。

本ポリシーの変更

本アプリの機能追加などにともない、本ポリシーを変更する場合があります。変更した場合は、本ページに変更後の内容と改定日を掲載します。情報の取り扱いに実質的な変更が生じる場合は、アプリの更新時にお知らせします。

お問い合わせ

本ポリシーまたは本アプリについてのお問い合わせは、以下までお願いします。

  • メール: takaakiyayoi@gmail.com

制定日: 2026年8月6日

iOSアプリ「いつぶり?」のプライバシーポリシー / Privacy Policy

プライバシーポリシー / Privacy Policy

iOSアプリ「いつぶり?」(以下「本アプリ」)のプライバシーポリシーです。


日本語

収集する情報

本アプリは、利用者の個人情報を一切収集しません。

氏名、メールアドレス、電話番号、位置情報、端末の識別子、利用状況など、いかなる情報も取得していません。

本アプリが端末に保存する情報

本アプリに登録した項目名・絵文字・カテゴリ・目安の間隔と、「やった」と記録した日付およびメモは、すべて利用者の端末内にのみ保存されます。開発者を含め、第三者がこれらを閲覧することはできません。

外部への送信

本アプリはネットワーク通信を行いません。記録した内容が外部のサーバーへ送信されることはありません。

第三者への提供

収集している情報がないため、第三者へ提供する情報もありません。

広告・アクセス解析

広告は表示しません。アクセス解析や利用状況計測のためのSDK等は一切組み込んでいません。

アカウント登録

アカウントの登録は不要です。ログインやメールアドレスの入力を求めることはありません。

バックアップ

端末の標準のバックアップ機能(iCloudバックアップ、パソコンへのバックアップ等)を有効にしている場合、本アプリの記録もそのバックアップに含まれます。これは Apple が提供する仕組みによるものであり、開発者がその内容にアクセスすることはできません。

データの削除

本アプリを端末から削除すると、記録した内容も端末から削除されます。

子どものプライバシー

収集する情報がないため、年齢を問わず安心してご利用いただけます。

本ポリシーの変更

内容を変更する場合は、本ページを更新してお知らせします。

お問い合わせ

本アプリに関するお問い合わせは、以下までお願いいたします。

  • takaakiyayoi@gmail.com

English

Information We Collect

This app collects no personal information whatsoever.

We do not obtain your name, email address, phone number, location, device identifiers, or any usage data.

Information Stored on Your Device

The items you add (name, emoji, category, and rough interval) and the dates and notes you record stay entirely on your own device. No one, including the developer, can view them.

Transmission

The app performs no network communication. Nothing you record is ever sent to any server.

Sharing with Third Parties

Since no information is collected, there is nothing to share with third parties.

Advertising and Analytics

The app shows no advertising and contains no analytics or usage-tracking SDKs of any kind.

Accounts

No account is required. You are never asked to sign in or provide an email address.

Backups

If you have your device's standard backup enabled (iCloud Backup or a backup to your computer), your records are included in that backup. This is handled by Apple's own mechanisms, and the developer has no access to its contents.

Deleting Your Data

Deleting the app from your device also removes your records from the device.

Children's Privacy

Because no information is collected, the app is safe to use at any age.

Changes to This Policy

If this policy changes, this page will be updated.

Contact

  • takaakiyayoi@gmail.com

制定日 / Effective date: 2026年8月5日