PRODUCT NOTE / PRD

SimpleMemoPad

ブラウザを使いながら、その場でひとことメモを残す。
この文書は「どう使うか」ではなく、なぜ・誰のために・どんな判断で作ったかを記録した開発ノートです。

ステータス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 ・ 開発ノート一覧
TL;DR

ひとことで

SimpleMemoPad は、ツールバーのアイコンかショートカット(Ctrl+Shift+Z)で開くメモ帳です。タブで複数のメモを持ち、打ったそばから手元(chrome.storage.local)に保存されます。

  • v1.6.0 で、開いたままにしたいときはサイドパネルへ移れるようにし、タブは最大 20 個まで増やして並べ替えられるようにしました。
  • v1.7.0 で、同じ Google アカウントで Chrome の同期を有効にしている PC どうしなら、同じメモが届くようにしました。拡張の中にログインもサーバーもありません。
  • v1.8.0 で、タブごとに「プレビュー」へ切り替えられるようにしました。Markdown・コードの色付け・Mermaid の図・式の計算結果を表示し、メモの途中に入れた画像もそこで見られます(2026-10-07 に公開)。
Problem

背景・解きたい課題

2023 年の初版時の設計文書は残っていないため、v1.5.1 までの動機はコミット履歴と実装から再構成した。v1.5.1 までの背景は推測

Who

ターゲットユーザー

プライマリ

運営者本人と、ストアから入れた少数のユーザー(2026-09-20 時点で 11 人)。拡張の中にアカウントを持たず、入れればすぐ使える形を保つ。同期は Chrome 側の設定に任せる。

主なユースケース

  • 調べ物の途中の URL・数字・キーワードを控える
  • 書きかけの文章や ToDo をタブごとに分けて置く
  • ページを見ながらメモを参照・追記する(サイドパネル)
  • まとめてテキストファイルに書き出す
Solution & Scope

解決策とスコープ

コア機能(In scope)

  • 打鍵ごとの自動保存(手元に保存し、Chrome の同期で別の PC へも届ける)
  • タブで複数メモ(最大 20 個・追加/削除/ドラッグで並べ替え)
  • ポップアップ表示と、サイドパネルへの移動
  • ショートカット(Ctrl+Shift+Z)で開く
  • 文字数表示・フォントサイズ・表示の拡大縮小
  • 全タブのテキストファイル書き出し
  • タブごとの編集/プレビュー切り替え(Markdown・コードの色付け・Mermaid・行末の式の計算)
  • 画像の挿入(ボタン・貼り付け・ドロップ)と、プレビュー上での拡大・ダウンロード

やらないこと(Non-goals)

  • 拡張独自のアカウント・自前の同期サーバー
  • リッチテキストの編集(書く場所は素のテキストのまま。見た目はプレビューで見せる)
  • 画像の同期(Chrome の同期に入る大きさではないので、画像はその PC にだけ置く)
  • タブの名前付け・検索(番号と色分けだけで足りる範囲に留める)
  • ページ内容の読み取り(権限は storage と sidePanel だけ。同期を足しても増やしていない)
Decision Log

主要な意思決定ログ

v1.8.0・v1.7.0・v1.6.0 の項目は実作業に基づく記録。初版 の項目は、コミット履歴と実装から読み取れる判断を再構成したもの。一部は実装から再構成

v1.8.0 書く場所は素のテキストのまま、見る場所だけをタブごとのプレビューに分ける。本文は信用せず、洗ってから表示する
DECISION

メモ帳を書式付きの編集欄にはしない。各タブに「プレビュー」を足し、押すとその場で Markdown・コードの色付け・Mermaid の図・式の計算結果に切り替わる。編集に戻れば元のテキストがそのまま出る。

プレビューにするかどうかはタブごとの設定で、chrome.storage.local にだけ持つ。同期しない。

  • 変換と色付けと図の描画に使うライブラリは、すべて拡張の中に同梱する。
  • 変換した HTML は、表示する前にすべてサニタイザ(DOMPurify)に通す。スクリプト・フォーム・埋め込み・style 属性・javascript: のリンクは落とす。
  • 外部 URL の画像は読み込まず、[画像: …] と出すだけにする。
WHY / 仮説

メモの本文は、v1.7.0 から別の PC でも書かれて届く。この PC で自分が書いたとは限らない入力を HTML にして画面へ出すことになる。信頼できる前提で描画すると、同期を経由して別の PC の画面でスクリプトが動きうる。

拡張の Manifest V3 は、外部のスクリプトを読み込めず、eval も使えない。CDN から読む案は取れないので同梱になり、図の描画ライブラリ(mermaid)が大半を占めて、展開後の拡張は約 5.7MB 増えた。

編集欄を書式付きにしなかったのは、v1.0 からの「開いて、書いて、閉じる」を崩したくなかったため。保存の形式(save_content)も変えずに済み、旧版のデータがそのまま読める。

RESULT
  • 権限は増えていない(storage と sidePanel のまま)。ストアの審査に出す理由欄も変わらない。
  • 提出用の ZIP は約 1.7MB。
  • 手元の確認では、<script> と onerror を入れた本文を描画しても、どちらも動かなかった。構文の誤った Mermaid は、エラー文と元のコードを出して止まる。
  • 2026-10-07 にストアへ提出し、同日に公開された。
v1.8.0 「画像は入れない」をやめる。ただし画像はその PC にだけ置き、同期しない
DECISION

これまでの「やらないこと」に入れていた画像を、メモに入れられるようにする。入れる方法は 3 つ。

  • タブの下の「画像を挿入」ボタン。プレビューを開いたままでも使える。
  • 貼り付け。
  • ドロップ。

画像は IndexedDB に置き、長辺 1600px までに縮めて WebP にする。メモの本文には ![](img:…) という参照だけが入る。

プレビューの画像はクリックで拡大でき、そこからダウンロードできる。上部の「画像」から、一覧・再挿入・削除ができる。

WHY / 仮説

きっかけは、Markdown の途中で画像を入れて、プレビューの上で見たいという要望だった。最初に作ったのは貼り付けとドロップだけで、プレビューを開いたままでは入れられず、ボタンもなかった。そこで「画像を挿入」を足した。

画像を Chrome の同期に載せる案は、最初から外した。1 項目が約 8KB、全体で約 100KB なので、写真は 1 枚も入らない。chrome.storage.local の上限(約 10MB)に置く案も、保存の形式(save_content)に画像を混ぜたくなくて取らなかった。

本文だけが同期で別の PC へ届くので、別の PC では参照だけが残る。そこでは壊れた画像ではなく、「この PC には保存されていません」と出す。

RESULT
  • 3000×2000 の画像を入れると、1600×1067 の WebP になって保存された(試験用の単色画像で約 3.5KB)。
  • 写真での容量は測っていない。
  • 権限は増えていない。unlimitedStorage も使っていない。
  • 画像を別の PC へ送る手段は無い。これは Roadmap に残した。
v1.8.0 式の計算は、行を書き換えずにプレビューの横へ結果だけ出す。計算と見なす行は狭く取る
DECISION

12*3+4 のように式だけの行と、=100/3 や 1,000 + 2,500 = のように = を付けた行の右に、結果を出す。メモの本文は変えない。

式は自前の小さな構文解析で読み、eval や new Function は使わない。

WHY / 仮説

メモには日付・電話番号・箇条書きの数字が普通に入る。2026-10-06 を引き算にしたり、- 3 + 4 の箇条書きに答えを付けたりすると、書いた覚えのない数字がメモに出る。

そこで = の無い行は、演算子が 1 つ以上あり、日付・電話番号の形でなく、箇条書きの先頭でもない場合だけを対象にした。

結果を本文へ書き戻さないのは、編集した式と古い答えが食い違ったまま残るのを避けるため。

RESULT
  • 18 通りの行(式・日付・電話番号・箇条書き・0 での割り算・括弧の閉じ忘れ)で、計算する行としない行を確かめた。
  • リストの直後の行に書いた式は、Markdown の仕様でリスト項目の続きとして扱われる。式の前に空行を入れれば避けられる。
v1.7.0 端末間の同期は Chrome 自身の同期(storage.sync)に乗せ、拡張の中にログインもサーバーも作らない
DECISION

画面(ポップアップ・サイドパネル)は、これまでどおり chrome.storage.local にだけ書く。

新しく置いた background(service worker)が、それを chrome.storage.sync と突き合わせる。同じ Google アカウントで Chrome の同期を有効にしている PC どうしで、同じタブを持つ。

sync 側はタブ 1 つを 1 項目にし、並びは別の項目に持つ。

  • 1 項目の上限(約 8KB、日本語で約 2,700 字)を超えたタブは、同期せずにその PC に残し、タブの下に理由を出す。
  • 全体の上限(約 100KB)を超えたときも、画面に知らせる。
  • どちらの場合もメモは消さない。
WHY / 仮説

同期を入れてこなかった理由は「アカウントも容量の制約も持ち込まずに、入れてすぐ使える形を保つ」ことだった(下の初版の項目)。

storage.sync なら、ログインは Chrome の側で済んでいるので拡張に手順が増えず、開発者のサーバーも通らない。持ち込むのは容量の制約だけで、それは「超えたタブは手元に残す」で吸収できると考えた。

  • タブ 1 つを 1 項目にしたのは、全タブを 1 項目にまとめると合計が 8KB で頭打ちになるため。
  • 同期は 1 分に 120 回・1 時間に 1,800 回までしか書けないので、打鍵のたびではなく、入力が 3 秒止まってから送る。
  • background は v1.6.0 で「割に合わない」として置かなかったもの。ポップアップを閉じたあとも最後の入力を送り切るには、画面の外に居場所が要る。
RESULT
  • 権限は増えていない(storage のまま)。
  • ストアの説明文と、「プライバシーへの取り組み」に書く storage の理由は、同期を含めて書き直した。
  • ストアへの提出は 2026-09-22 に完了し、v1.7.0 が公開中。
v1.7.0 タブに ID を振り、前回の同期内容を基準に 3 者で比べる。編集は削除に勝ち、同じタブは書いた側の全文が勝つ
DECISION

タブごとに変わらない ID を持たせた(save_ids。save_content の形式は変えていない)。

同期のたびに「そのとき sync が持っていた内容」(sync_base)を残す。次の同期では、手元・sync・基準の 3 つを比べてタブごとに決める。基準から変わっている側を「書き換えた側」とみなす。

  • 手元で消したタブを別の PC が書き換えていたら、編集が勝って残る。
  • 同じタブを両方で書き換えていたら、この PC の全文が勝つ(文字単位には混ぜない)。
  • 初めて同期する PC では sync にあるメモを取り、手元の空のタブと、同じ中身のタブは足さない。
WHY / 仮説

基準を持たずに手元と sync の 2 つだけを比べると、別の PC で足したタブと「この PC で消したタブ」が同じ形に見え、次の同期で消してしまう。

何番目のタブかで対応を取ると、v1.6.0 で入れた追加・削除・並べ替えのたびに、別のタブと取り違える。

編集を削除に勝たせたのは、v1.6.0 の「書いたメモを消す経路を作らない」を同期でも守るため。削除は空のタブにしかできないので、削除が負けても失うものは無い。

初回に空のタブを落とすのは、2 台目に入れた時点で、既定の空のタブ 7 個が 1 台目にまで増えないようにするため。

RESULT

merge.js 単体で、次の 6 通りを確かめた(2026-09-22)。

  • 別の PC で足したタブが残る
  • 手元で消したタブが消える
  • 消したタブを向こうが書き換えていたら残る
  • 同じタブは手元の全文が勝つ
  • 初回は空のタブと重複を落とす
  • 手元の並べ替えが勝つ

同じタブを 2 台でほぼ同時に書き換えると、片方の書き換えは残らない。

v1.7.0 外からの変更は使っている画面にも入れる。守るのは「いま書いているタブ」だけにする
DECISION

v1.6.0 では、フォーカスのある画面は保存内容を読み直さなかった。

v1.7.0 では、どの画面も外からの変更(もう 1 つの画面と、別の PC)を取り込む。直前 5 秒以内に入力があったタブの中身だけは書き換えない。

表示を組み直すときは既存のタブを作り直さずに使い回し、並びが変わって入力欄のフォーカスが外れたら戻す。

WHY / 仮説

別の PC の変更は、いま使っている画面にも届く。保存は全タブ一括のままなので、使っている画面が取り込まずにいると、次の 1 文字で全タブが書き戻されて、届いた変更が消える。

v1.6.0 で「使っていない側だけ」にしたのは、入力中にカーソルが飛ぶのを避けるためだった。守る範囲を書いているタブ 1 つに絞れば両立する。

RESULT

書いている最中のタブに別の PC の変更が届いた場合は、書いている側が勝つ(次の保存で上書きする)。

3 者比較の「同じタブは書いた側の全文」と同じ結果になるので、2 つの規則が食い違う場面は作っていない。

v1.6.0 ポップアップは残したまま、サイドパネルへ「移る」ボタンを足す
DECISION

アイコンと Ctrl+Shift+Z は、従来どおりポップアップを開く。ポップアップ内の「サイドパネルに固定」ボタンを押すと、そのウィンドウのサイドパネルへ移る。

サイドパネルをすぐ開き直したい人には、Chrome 自身のパネル見出しにあるピン(ツールバーへの固定)を使ってもらう。

「固定した状態を覚えて、以後はアイコンでもサイドパネルを開く」案は採らなかった。

WHY / 仮説

ポップアップはページをクリックすると閉じるので、メモを見ながらページを操作できない。Quick Query Question が v2.0.0 でポップアップを捨てたのと同じ構造の問題だった。

ただしメモ帳は「さっと書いて閉じる」使い方も主用途で、ポップアップを捨てる理由はない。

状態を覚える方式にすると、切り替え状態を持つ background(service worker)が要る。ポップアップとサイドパネルの 2 画面だけの拡張に持ち込む実装コストではないと判断した。

RESULT
  • HTML は popup.html 1 枚のままにし、サイドパネルは popup.html?mode=panel で開く。
  • クエリ付きのパスが manifest の検証を通るかは、Chromium のソースで確かめた(ファイルの存在確認に使う url.path() はクエリを含まない)。
  • 追加した権限は警告の出ない sidePanel だけで、最低バージョンは Chrome 116 になった。
  • 最初はピンのアイコンだけのボタンにしたが目立たず、青地に「サイドパネルに固定」と書いたボタンに改めた。
v1.6.0 2 つの画面が同時に開けるので、使っていない側は保存のたびに読み直す
DECISION

chrome.storage.onChanged を受けて、フォーカスの無い画面だけ保存内容を読み直して表示を差し替える。

v1.7.0 で、使っている画面にも取り込む形に改めた(上の項目)。

WHY / 仮説

保存処理は、全タブを 1 つの配列にまとめて書き込む。

サイドパネルを開いたままポップアップで書くと、パネル側には古い内容が残る。その状態でパネルに 1 文字打つと、ポップアップで書いた内容がまるごと上書きされて消える。サイドパネル対応で初めて生まれた壊れ方で、設計中に気づいた。

フォーカスのある側まで読み直すと入力中にカーソルが飛ぶので、差し替えるのは使っていない側だけにした。

RESULT

片方でタブを追加・削除・移動・入力すると、もう片方に反映されることを自動テストで確認した。

保存の単位が「全タブ一括」のままなので、上書きの危険そのものは構造として残っている。タブ単位で保存するようにすれば根本から消える。

v1.6.0 タブの 7 個固定をやめ、最大 20 個まで増減・並べ替えできるようにする。削除は中身を空にしてから
DECISION

HTML に決め打ちしていた 7 組(ラジオボタン・ラベル・入力欄。表示の切り替えは CSS の nth-child)をやめ、保存データから JS で組み立てる形にした。

  • 「+」で追加(上限 20)
  • タブをドラッグして並べ替え
  • 削除は中身が空のタブだけ

保存形式(各タブの中身の配列+末尾に選択中のタブ番号)は変えていない。

WHY / 仮説
  • 上限の 20 は、ポップアップ幅でもサイドパネル幅でも 3 行に折り返して全タブが見え、番号が 2 桁に収まる数として選んだ。
  • 削除を「空にしてから」に限ったのは、書いたメモを誤って消す経路を作らないため。確認ダイアログは読まずに押されうるので、消える中身が無い状態でしか消せないほうが確実だと考えた。
  • 保存形式を変えなかったのは、既存ユーザーのメモを移行処理なしでそのまま読むため。
RESULT

自動テスト 35 項目が通った。次のことを含む。

  • 旧形式のデータ(7 個+選択番号)がそのまま読める
  • 20 個で「+」が止まる
  • 中身があるタブの削除ボタンが押せない
  • 並べ替えても、選択中のメモが選択されたまま

タブが複数行に折り返したとき、上の行で選んだタブが下の枠線とつながらず浮いて見える点は、直さずに残した。

初版 保存ボタンを置かず、打つたびにブラウザ内へ保存する
DECISION

保存操作を無くし、入力のたびに全タブを chrome.storage.local に書き込む。

タブ機能を入れた 2023-04-18 の時点からこの形で、v1.0 で選択中のタブ番号も一緒に保存するようになった。

端末間の同期(storage.sync)は使わない(v1.7.0 で使い始めた。上の項目)。

WHY / 仮説

メモ帳でいちばん困るのは、保存し忘れて消えること。保存という操作そのものを無くせば、忘れようがない。

同期しないのは、アカウントも容量の制約も持ち込まずに、入れてすぐ使える形を保つためと読める。実装から再構成

RESULT

画面の下に「Auto save in local env...」と出して、保存の心配が要らないことを伝えている。

反面、書き出し(v1.2)以外にメモを外へ持ち出す手段は無かった(v1.7.0 で同期が入るまで)。

Architecture

構成メモ

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.jsservice 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 項目にしている
Milestones

これまでの歩み

v1.8.02026-10-07

タブごとのプレビュー(Markdown・コードの色付け・Mermaid)/式の自動計算/画像の挿入・拡大・ダウンロード(ストアに公開)

v1.7.02026-09-21

Chrome の同期で別の PC にも同じメモ/タブに ID を振って 3 者比較(足したタブは消さず、編集は削除に勝つ)/同期できない長さのタブは手元に残して知らせる

v1.6.02026-09-20

サイドパネルへ移るボタン(ポップアップも残す)/タブを最大 20 個まで追加・空にして削除・ドラッグで並べ替え/ポップアップとサイドパネルの内容を同期

v1.5.0 〜 v1.5.12023-09-04

タブごとの文字数表示/コード整形

v1.3.2 〜 v1.3.42023-05-16

ショートカットキー(Ctrl+Shift+Z)で開けるように

v1.1 〜 v1.3.12023-04-19 〜 2023-04-27

拡大縮小とフォントサイズ選択/テキストファイル書き出し/閉じるボタンと入力済みタブの色分け

v1.02023-04-19

7 個のタブ/新デザイン/選択中のタブも保存し、開き直すと同じタブに戻る

Roadmap / Open Questions

これから

未決事項:タブの名前付けを入れるかどうか。