PRODUCT NOTE / PRD
ブラウザを使いながら、その場でひとことメモを残す。
この文書は「どう使うか」ではなく、なぜ・誰のために・どんな判断で作ったかを記録した開発ノートです。
| ステータス | Shipped / Active(Chrome ウェブストアで公開中) |
|---|---|
| 現行バージョン | v1.8.0(公開中。2026-10-07 にストアへ提出し、同日に公開された) |
| 初版 | 2023-04-13(最初のコミット)・2023-04-19 に v1.0 |
| 提供形態 | Chrome 拡張機能(Manifest V3 / v1.6.0 から Chrome 116 以降)・一般公開 |
| 利用者 | 11 ユーザー(2026-09-20 時点のストア表示) |
| 最終更新 | 2026-10-07 |
| 技術 | Manifest V3 ・ chrome.storage.local / sync ・ IndexedDB(画像)・ service worker(同期だけ)・ chrome.sidePanel ・ 素の HTML / CSS / JavaScript(ビルドなし)・ 同梱ライブラリ(marked / DOMPurify / highlight.js / mermaid) |
| 関連リンク | Chrome ウェブストア ・ GitHub ・ 開発ノート一覧 |
SimpleMemoPad は、ツールバーのアイコンかショートカット(Ctrl+Shift+Z)で開くメモ帳です。タブで複数のメモを持ち、打ったそばから手元(chrome.storage.local)に保存されます。
2023 年の初版時の設計文書は残っていないため、v1.5.1 までの動機はコミット履歴と実装から再構成した。v1.5.1 までの背景は推測
運営者本人と、ストアから入れた少数のユーザー(2026-09-20 時点で 11 人)。拡張の中にアカウントを持たず、入れればすぐ使える形を保つ。同期は Chrome 側の設定に任せる。
storage と sidePanel だけ。同期を足しても増やしていない)v1.8.0・v1.7.0・v1.6.0 の項目は実作業に基づく記録。初版 の項目は、コミット履歴と実装から読み取れる判断を再構成したもの。一部は実装から再構成
メモ帳を書式付きの編集欄にはしない。各タブに「プレビュー」を足し、押すとその場で Markdown・コードの色付け・Mermaid の図・式の計算結果に切り替わる。編集に戻れば元のテキストがそのまま出る。
プレビューにするかどうかはタブごとの設定で、chrome.storage.local にだけ持つ。同期しない。
style 属性・javascript: のリンクは落とす。[画像: …] と出すだけにする。メモの本文は、v1.7.0 から別の PC でも書かれて届く。この PC で自分が書いたとは限らない入力を HTML にして画面へ出すことになる。信頼できる前提で描画すると、同期を経由して別の PC の画面でスクリプトが動きうる。
拡張の Manifest V3 は、外部のスクリプトを読み込めず、eval も使えない。CDN から読む案は取れないので同梱になり、図の描画ライブラリ(mermaid)が大半を占めて、展開後の拡張は約 5.7MB 増えた。
編集欄を書式付きにしなかったのは、v1.0 からの「開いて、書いて、閉じる」を崩したくなかったため。保存の形式(save_content)も変えずに済み、旧版のデータがそのまま読める。
storage と sidePanel のまま)。ストアの審査に出す理由欄も変わらない。<script> と onerror を入れた本文を描画しても、どちらも動かなかった。構文の誤った Mermaid は、エラー文と元のコードを出して止まる。これまでの「やらないこと」に入れていた画像を、メモに入れられるようにする。入れる方法は 3 つ。
画像は IndexedDB に置き、長辺 1600px までに縮めて WebP にする。メモの本文には  という参照だけが入る。
プレビューの画像はクリックで拡大でき、そこからダウンロードできる。上部の「画像」から、一覧・再挿入・削除ができる。
きっかけは、Markdown の途中で画像を入れて、プレビューの上で見たいという要望だった。最初に作ったのは貼り付けとドロップだけで、プレビューを開いたままでは入れられず、ボタンもなかった。そこで「画像を挿入」を足した。
画像を Chrome の同期に載せる案は、最初から外した。1 項目が約 8KB、全体で約 100KB なので、写真は 1 枚も入らない。chrome.storage.local の上限(約 10MB)に置く案も、保存の形式(save_content)に画像を混ぜたくなくて取らなかった。
本文だけが同期で別の PC へ届くので、別の PC では参照だけが残る。そこでは壊れた画像ではなく、「この PC には保存されていません」と出す。
unlimitedStorage も使っていない。12*3+4 のように式だけの行と、=100/3 や 1,000 + 2,500 = のように = を付けた行の右に、結果を出す。メモの本文は変えない。
式は自前の小さな構文解析で読み、eval や new Function は使わない。
メモには日付・電話番号・箇条書きの数字が普通に入る。2026-10-06 を引き算にしたり、- 3 + 4 の箇条書きに答えを付けたりすると、書いた覚えのない数字がメモに出る。
そこで = の無い行は、演算子が 1 つ以上あり、日付・電話番号の形でなく、箇条書きの先頭でもない場合だけを対象にした。
結果を本文へ書き戻さないのは、編集した式と古い答えが食い違ったまま残るのを避けるため。
storage.sync)に乗せ、拡張の中にログインもサーバーも作らない画面(ポップアップ・サイドパネル)は、これまでどおり chrome.storage.local にだけ書く。
新しく置いた background(service worker)が、それを chrome.storage.sync と突き合わせる。同じ Google アカウントで Chrome の同期を有効にしている PC どうしで、同じタブを持つ。
sync 側はタブ 1 つを 1 項目にし、並びは別の項目に持つ。
同期を入れてこなかった理由は「アカウントも容量の制約も持ち込まずに、入れてすぐ使える形を保つ」ことだった(下の初版の項目)。
storage.sync なら、ログインは Chrome の側で済んでいるので拡張に手順が増えず、開発者のサーバーも通らない。持ち込むのは容量の制約だけで、それは「超えたタブは手元に残す」で吸収できると考えた。
storage のまま)。タブごとに変わらない ID を持たせた(save_ids。save_content の形式は変えていない)。
同期のたびに「そのとき sync が持っていた内容」(sync_base)を残す。次の同期では、手元・sync・基準の 3 つを比べてタブごとに決める。基準から変わっている側を「書き換えた側」とみなす。
基準を持たずに手元と sync の 2 つだけを比べると、別の PC で足したタブと「この PC で消したタブ」が同じ形に見え、次の同期で消してしまう。
何番目のタブかで対応を取ると、v1.6.0 で入れた追加・削除・並べ替えのたびに、別のタブと取り違える。
編集を削除に勝たせたのは、v1.6.0 の「書いたメモを消す経路を作らない」を同期でも守るため。削除は空のタブにしかできないので、削除が負けても失うものは無い。
初回に空のタブを落とすのは、2 台目に入れた時点で、既定の空のタブ 7 個が 1 台目にまで増えないようにするため。
merge.js 単体で、次の 6 通りを確かめた(2026-09-22)。
同じタブを 2 台でほぼ同時に書き換えると、片方の書き換えは残らない。
v1.6.0 では、フォーカスのある画面は保存内容を読み直さなかった。
v1.7.0 では、どの画面も外からの変更(もう 1 つの画面と、別の PC)を取り込む。直前 5 秒以内に入力があったタブの中身だけは書き換えない。
表示を組み直すときは既存のタブを作り直さずに使い回し、並びが変わって入力欄のフォーカスが外れたら戻す。
別の PC の変更は、いま使っている画面にも届く。保存は全タブ一括のままなので、使っている画面が取り込まずにいると、次の 1 文字で全タブが書き戻されて、届いた変更が消える。
v1.6.0 で「使っていない側だけ」にしたのは、入力中にカーソルが飛ぶのを避けるためだった。守る範囲を書いているタブ 1 つに絞れば両立する。
書いている最中のタブに別の PC の変更が届いた場合は、書いている側が勝つ(次の保存で上書きする)。
3 者比較の「同じタブは書いた側の全文」と同じ結果になるので、2 つの規則が食い違う場面は作っていない。
アイコンと Ctrl+Shift+Z は、従来どおりポップアップを開く。ポップアップ内の「サイドパネルに固定」ボタンを押すと、そのウィンドウのサイドパネルへ移る。
サイドパネルをすぐ開き直したい人には、Chrome 自身のパネル見出しにあるピン(ツールバーへの固定)を使ってもらう。
「固定した状態を覚えて、以後はアイコンでもサイドパネルを開く」案は採らなかった。
ポップアップはページをクリックすると閉じるので、メモを見ながらページを操作できない。Quick Query Question が v2.0.0 でポップアップを捨てたのと同じ構造の問題だった。
ただしメモ帳は「さっと書いて閉じる」使い方も主用途で、ポップアップを捨てる理由はない。
状態を覚える方式にすると、切り替え状態を持つ background(service worker)が要る。ポップアップとサイドパネルの 2 画面だけの拡張に持ち込む実装コストではないと判断した。
popup.html 1 枚のままにし、サイドパネルは popup.html?mode=panel で開く。url.path() はクエリを含まない)。sidePanel だけで、最低バージョンは Chrome 116 になった。chrome.storage.onChanged を受けて、フォーカスの無い画面だけ保存内容を読み直して表示を差し替える。
v1.7.0 で、使っている画面にも取り込む形に改めた(上の項目)。
保存処理は、全タブを 1 つの配列にまとめて書き込む。
サイドパネルを開いたままポップアップで書くと、パネル側には古い内容が残る。その状態でパネルに 1 文字打つと、ポップアップで書いた内容がまるごと上書きされて消える。サイドパネル対応で初めて生まれた壊れ方で、設計中に気づいた。
フォーカスのある側まで読み直すと入力中にカーソルが飛ぶので、差し替えるのは使っていない側だけにした。
片方でタブを追加・削除・移動・入力すると、もう片方に反映されることを自動テストで確認した。
保存の単位が「全タブ一括」のままなので、上書きの危険そのものは構造として残っている。タブ単位で保存するようにすれば根本から消える。
HTML に決め打ちしていた 7 組(ラジオボタン・ラベル・入力欄。表示の切り替えは CSS の nth-child)をやめ、保存データから JS で組み立てる形にした。
保存形式(各タブの中身の配列+末尾に選択中のタブ番号)は変えていない。
自動テスト 35 項目が通った。次のことを含む。
タブが複数行に折り返したとき、上の行で選んだタブが下の枠線とつながらず浮いて見える点は、直さずに残した。
保存操作を無くし、入力のたびに全タブを chrome.storage.local に書き込む。
タブ機能を入れた 2023-04-18 の時点からこの形で、v1.0 で選択中のタブ番号も一緒に保存するようになった。
端末間の同期(storage.sync)は使わない(v1.7.0 で使い始めた。上の項目)。
メモ帳でいちばん困るのは、保存し忘れて消えること。保存という操作そのものを無くせば、忘れようがない。
同期しないのは、アカウントも容量の制約も持ち込まずに、入れてすぐ使える形を保つためと読める。実装から再構成
画面の下に「Auto save in local env...」と出して、保存の心配が要らないことを伝えている。
反面、書き出し(v1.2)以外にメモを外へ持ち出す手段は無かった(v1.7.0 で同期が入るまで)。
background(service worker)は同期のためだけに持つ(v1.7.0)。
画面は chrome.storage.local にだけ書き、chrome.storage.sync との突き合わせは background が行う。
ポップアップとサイドパネルは同じ popup.html を開き、クエリで見た目だけを切り替える。
| 構成要素 | 役割 |
|---|---|
popup.html | ポップアップとサイドパネルで共用。サイドパネルは manifest の side_panel.default_path で popup.html?mode=panel を開く |
mode.js | 描画前にクエリを見て <html> に .panel を付ける。サイドパネル用のレイアウトは CSS 側で切り替える |
tabs.js | タブの組み立て・保存・追加・削除・並べ替えと、外からの変更(もう 1 つの画面・別の PC)の取り込み。同期できなかったタブの知らせもここで出す |
background.js | service worker。storage.local が変わったら 3 秒、storage.sync が変わったら 0.5 秒待ってから突き合わせる。起動時・インストール時・画面を開いたときにも走る。突き合わせは 1 回ずつ順番に行う |
merge.js | 手元・sync・前回の同期内容の 3 者比較。background から読み込む |
pin.js | サイドパネルを開く。chrome.sidePanel.open() はユーザー操作の直後にしか呼べないので、クリックの処理の中で await を挟まずに呼ぶ |
preview.js / calc.js | プレビューの描画(v1.8.0)。Markdown を変換し、サニタイザを通してから、コードの色付け・Mermaid・画像の参照を処理する。calc.js は式だけの行の計算 |
images.js / gallery.js | 画像の保存(縮小して IndexedDB へ)・取り出しと、「画像」一覧・拡大表示・ダウンロード(v1.8.0) |
lib/ | 同梱したライブラリ。外部スクリプトを読めないので、版を固定して拡張の中に置く。版とライセンスは同じ場所の一覧に書いてある |
zoom.js / download.js | フォントサイズ・拡大縮小・閉じるボタンと、全タブのテキスト書き出し |
| 保存(手元) | chrome.storage.local の save_content = [メモ1, …, メモN, 選択中のタブ番号](v1.0 から形式を変えていない)。v1.7.0 でタブの ID の並び save_ids、前回の同期内容 sync_base、同期できなかったタブと失敗の理由 sync_status を足した。v1.8.0 でプレビューにしているタブの記録 save_modes を足した(同期しない) |
| 保存(画像) | IndexedDB に 1 枚 1 件(ID・画像本体・大きさ・元のファイル名)。chrome.storage には置かない。同期しない |
| 保存(同期) | chrome.storage.sync の memo_order = [タブの ID, …] と、memo_<タブの ID> = 本文。1 項目の上限が約 8KB なので、タブ 1 つを 1 項目にしている |
タブごとのプレビュー(Markdown・コードの色付け・Mermaid)/式の自動計算/画像の挿入・拡大・ダウンロード(ストアに公開)
Chrome の同期で別の PC にも同じメモ/タブに ID を振って 3 者比較(足したタブは消さず、編集は削除に勝つ)/同期できない長さのタブは手元に残して知らせる
サイドパネルへ移るボタン(ポップアップも残す)/タブを最大 20 個まで追加・空にして削除・ドラッグで並べ替え/ポップアップとサイドパネルの内容を同期
タブごとの文字数表示/コード整形
ショートカットキー(Ctrl+Shift+Z)で開けるように
拡大縮小とフォントサイズ選択/テキストファイル書き出し/閉じるボタンと入力済みタブの色分け
7 個のタブ/新デザイン/選択中のタブも保存し、開き直すと同じタブに戻る
storage.local への保存はまだ全タブ一括で、上書きし合う危険は「書いているタブを守る」で抑えているだけ。未決事項:タブの名前付けを入れるかどうか。