# 個人用LOGの設計と作業記録

NagiMangaには、漫画をファイルで保存する管理画面があります。
そこに、短い文章と画像を気軽に投稿できるLOGを追加します。
作業ブランチは `feature/personal-log-cms` です。

## 保存のしかた

投稿は1件ごとに保存します。ノートのページを1枚ずつ増やす形です。
データベースや外部のJavaScriptライブラリは追加しません。
ログイン、CSRF対策、画像の読み直しはNagiMangaと共用します。

URLの投稿番号は、日本時間の `20261004123033` のような日時です。
同じ秒に重なった場合は `20261004123033-02` とします。
番号は公開するときに決まり、編集しても変わりません。
下書きは別の番号で保存し、公開するまでは外から読めません。

## 画面と本文

NagiMemoの `skin-cover.html`、`common-ui.js`、`drag-drop-upload.js` を参考にします。
画面、投稿操作、保存処理を別のファイルに分けます。
NagiMemoのてがろぐ用の処理や記号はコピーしません。

パソコンは投稿欄と一覧、スマホは「書く」から開く投稿画面です。
選んだ文字を太字にでき、画像は選択、貼り付け、ドロップで追加できます。
本文のHTMLは文字として表示します。

漫画は、新しい作品からタイトルと表紙を選びます。
本文には `[Mangaタイトル名]` を入れ、別に作品IDを保存します。
同名の作品には短い識別番号を添えます。
タグは本文の好きな位置へ移せます。
パスワード付き漫画の表紙は公開側へ出しません。

画像にも番号を付けます。差し替えても本文のタグは変わりません。
差し替えた画像には新しい版の番号を付け、古いキャッシュを避けます。
未使用画像と下書きの画像は、公開窓口から読めません。

## 確認すること

投稿、編集、下書き、公開、画像追加、差し替え、漫画選択を確認します。
同じ秒の投稿、二重送信、別タブの編集も確認します。
管理者以外の書き込み、HTMLの実行、非公開画像の漏れを確認します。
漫画の既存テストと、LOGのバックアップ・復元も実行します。
スマホの投稿画面と、画像・漫画ビューアーはブラウザで確認します。

## 参照元

[NagiMemo](https://github.com/Lichiphen/NagiMemo) の参照コミットは
`e2fc9df1af817e9ca2fd09f1c444698c1b90edf9` です。
NagiMemoとNagiSwipeは、ともにLichiphenのMITライセンスです。
自動更新が公開済みの旧版を選ばないよう、開発ブランチの版番号も更新します。
コミット、push、公開サイトへの設置は、依頼があったときに進めます。

## 実装の確認

一覧用の小さなファイルを追加しました。
一覧はそこから必要な20件の本文だけを読みます。
画像を公開するかどうかは、投稿の原本でも確認します。

LOGの145項目と、漫画CMSの186項目を確認しました。
ブラウザでは390×844の投稿、太字、画像追加、保存を操作しました。
公開LOGからの画像拡大と漫画ビューアーの起動も確認しました。

## 追加した仕様と判断

公開入口を `index.php` にして、URLは設置ディレクトリとクエリだけにしました。
以前の `log.php` は、新しいURLへ301転送します。
ファイル名を隠す処理と、認証・入力・保存先の保護は別の役割です。

カテゴリは、投稿時の作成・選択と、設定画面からの改名に対応します。
ハッシュタグは本文から抽出し、直近8件を投稿欄へ表示します。
改名時は、公開済みの本文と下書きも書き直し、古い編集からの上書きを防ぎます。

サイドメニューに、カレンダー、最新3件、分類、最終更新日を追加しました。
スマホでは、ひし形のボタンに三本線を描き、右上へ固定します。
投稿の白い部分は幅いっぱいに広げ、文字の内側に余白を残しました。
ライト3種類・ダーク3種類の設定、共通アイコン、丸いスクロールバーを追加しています。
色見本は全体を選択でき、スマホのラジオボタンは見本の下です。

トップとカテゴリの先頭ページだけを検索の対象にします。
個別投稿、タグ、日付、月、2ページ目以降はnoindexです。
個別OGPは本文の最初の画像を優先し、画像がなければ設定した共通OGPを使います。
漫画カードの表紙と、投稿URLのOGPは別に選びます。

画像収集を示すUser-Agentと、同じIPからの画像大量取得を404で拒否します。
LOG画像と漫画画像に共通の制限を使います。
文章閲覧のAIを一律に拒否せず、UA偽装・低頻度・分散取得の完全排除は保証しません。
404のブラックホールはCSSで描き、動きを減らす設定にも対応しました。

キャッシュバスターは、版番号だけでなくJS・CSSの内容から作ります。
漫画CSSだけを変えたときも、CSSを読み込むJSの番号を変えます。
画像差し替えは、同じ画像IDと新しい版番号を使います。

## 独立監査での修正

別Agentによる機能監査と敵対検証で、数字だけのカテゴリIDの扱いを修正しました。
旧形式の数値IDも文字列として読み直し、公開・管理画面、絞り込み、復元へ反映します。
同じ秒の100件目以降は、連番を数値として比べて並べます。
試験URLと、最新3件の部分一致による誤判定も修正しました。

修正後はLOG145項目・既存CMS186項目を2回ずつ、独立敵対検証84項目を確認しています。
結果と未確認の範囲は [監査記録](personal-log-audit.md) に残しました。
新しい [README](../README-LOG.md) には、メリット・デメリット、操作、保存先、引っ越しをまとめています。
文章はsoft-japanese-writingと、指定されたyomiyasuで整えました。

## タイトル行の表示を修正

追加の依頼に合わせて、入力本文の1行目をタイトル、2行目以降を本文にしました。
タイトルの文を本文で繰り返さず、公開・管理一覧・プレビューで同じ表示にします。
1行目に画像・漫画のタグがある場合は、本文へ残して表示します。
投稿と編集欄の原文は変更しません。
長いタイトルは見出しに全文を出し、共有情報とサイドメニューだけ80文字で省略します。
一覧ファイルの形式番号を5にして、以前のタイトル情報も原本から読み直します。
この変更を含めて、別AgentがLOG153項目を2回、敵対検証99項目を確認しました。

## 管理者の投稿と自分専用Memo

既存のCMSをコミット5e46a72に保存しました。追加の依頼は同じブランチで作業し、プッシュしません。
ログイン中のサイトでは、メニューの名称を「管理ページ」にし、記事に編集ボタンを追加します。
ログイン・管理ページへのリンクをまとめたブロックは、設定で隠せます。
先頭の記事の上に投稿欄を置き、スクロールで隠れるとペンのSVGと「書く」を表示します。
スマホとパソコンの両方から、浮遊ボタンで投稿画面を開けます。

LOGの公開範囲は、全体公開と自分専用Memoから選びます。
Memoでは、管理者としてログインした場合だけ記事とLOG画像を読めます。
未ログイン・ゲスト・失効したログインでは404を返します。漫画の公開範囲は別の設定です。
認証Cookieの範囲を管理フォルダから設置ルートへ移し、既存ログインも移行します。
ログイン中のサイト・画像はprivate,no-store、公開応答にはVary:Cookieを指定します。

カテゴリ・ハッシュタグは「LOG・投稿」へ移します。
設定はNagiMANGA、LOG、共通・安全のタブに分け、各ブロックの保存先を分かるようにします。
トップの管理メニューは、ペンのSVGと3つの主な入口をまとめます。
作品用の追加操作、バックアップ、更新、ログアウトは「管理メニュー」に置きます。

サイトのトップ、日付・月、カテゴリ、タグは共通で10件表示を標準とし、設定で1〜100件へ変えられます。
管理一覧は20件を標準とし、一覧の上部で1〜100件へ変えられます。管理側の件数はセッションに保存します。
管理一覧にはテーブルを使わず、日時、タイトル、小さな画像、編集、削除を短い行で並べます。

## フッター・分類順序・一括削除

フッターは「LOG → サイト下部の表記」で表示を選び、1行200文字まで自由に入力できます。
HTMLは実行せず文字として表示します。空欄でも表示を隠します。
カテゴリとハッシュタグは、持ち手のドラッグと上下ボタンで並び替えます。
マウスとタッチをPointer Eventsで処理し、変更はその場で保存します。
分類の更新番号を照合し、別画面の変更を上書きしません。保存失敗時には表示の順番も戻します。
投稿欄の最近8件のタグは、使用履歴から選ぶ機能として維持します。

管理一覧に「記事を見る」を追加します。下書きは公開URLへ進めず、編集から確認します。
一覧の上部で一括削除モードを開き、個別選択・ページ内全選択・選択件数を表示します。
確認画面を経てから削除し、一括選択中は浮遊する投稿ボタンを隠します。

画像の清掃範囲は利用者に確認し、「その記事だけで使っていた画像・サムネイル・関連記録も削除する」で了承されました。
ほかの記事・下書き・アイコン・OGPで使っている画像は残します。
無関係な未使用画像、漫画の原本、分類の設定、セキュリティログは一斉に消しません。
保存済みのローカル投稿データは維持し、削除試験は専用コピーで行います。

削除する全記事の更新番号を事前確認し、その後、一時フォルダへ記事と専用画像を移します。
共有画像の参照は一覧用ファイルではなく、残る投稿の原本から確認します。
受付記録と一覧情報も更新し、途中で失敗した場合は記事・画像・受付記録・一覧を戻します。
正常時には一時フォルダを消し、空になった年・下書きフォルダも整理します。
空の案内用index.htmlだけが残る場合も整理し、ほかの内容を持つフォルダは消しません。

停電、強制終了、書き込み権限やディスクの故障まで、完全な復旧を保証する設計にはしていません。
削除は取り消せないため、READMEでバックアップからの復元方法を説明します。
新規投稿の再送防止記録も整理するので、古い新規投稿欄の再送は新しい記事になる場合があります。
削除後は古い投稿欄を閉じ、画面を開き直すよう案内します。

フッターと分類の並び順をLOGバックアップに含め、旧形式の復元では保存先の既存設定を保ちます。
ローカル起動用start_preview.ps1は、保存済みのLOGを再利用します。データの再作成や初期化を行いません。
Windows PowerShell5でも使えるよう、UTF-8のBOMと、引用符を壊さないPHP要件検査にしました。

## 404の描画を更新

小さな円の描画を、画面いっぱいのSVGへ変えました。
降着円盤、曲がる光の輪、黒い中心、星空、光のにじみを重ね、CSSでゆっくり動かします。
外部画像やJavaScriptは使いません。動きを減らす端末設定では静止します。
スマホでも戻るボタンが画面に収まり、通常の404・HEAD・noindex・キャッシュ拒否の扱いは共通です。

## 追加監査で直した条件

削除の途中失敗で、空のindex.htmlを持つ一時フォルダが残りました。
最後の年別記事でも同じ案内ファイルが残ったため、空の案内だけを整理する処理に直しました。
数字だけの画像IDがPHPの配列キーで整数になる場合も、読み込む前に文字列へそろえました。

数字のキーを持つJSONオブジェクトは、PHPで配列へ変換するとリストに見える場合がありました。
一括削除と分類順序のAPIでは、変換前のJSONの型も確かめ、配列だけを受け付けます。
更新番号のHTTP入力も数字の桁を検査し、不正な文字を整数へ変換して受け入れないようにしました。
再確認の結果と限界は、監査記録とREADMEに記載します。
# 2026-10-04の追加：サイドバーの編集

ボタンのSVGアイコンを縦中央へそろえる依頼を受け、管理画面のボタンをinline-flexに変更しました。
スマホの「書く」ボタンも、アイコンと文字を中央へそろえます。

サイドバーの7つの標準項目に、表示切替と並び替えを追加しました。
ドラッグと上下ボタンを使い、順番と編集内容は保存ボタンでまとめて反映します。
スマホのメニューにも、同じ順番を使います。

上級者向けのHTML枠を、グリッドリンク・枠なしバナー・自由なHTMLから追加できます。
同じテンプレートを繰り返し呼び出せます。枠の個数は決めず、1枠50KB・全体1MBで内容量を制限します。
見出しと枠の表示は変更でき、バナーは既定で白い枠・背景を出しません。
管理者が入力したHTMLも、DOMから許可した要素・属性・URLだけを組み直します。
外部HTTPS画像は、設定した画像の配信元だけを公開ページのCSPへ追加します。
LOG画像への画像タグとリンクは共有参照に含め、記事削除・単体画像削除から保護します。

保存先は`data/log/sidebar.php`です。更新番号で別タブからの古い上書きを拒否します。
LOGのZIPにも保存し、旧形式のZIPでは復元先のサイドバーを維持します。
復元時にもHTMLを安全化し、失敗時は他のLOG記録と一緒に巻き戻します。

監査で、HTML安全化後の文字数増加と、画像URLの別表記による保護漏れを修正しました。
安全化後にも50KBを確認し、画像URLは公開ルートを基準に相対パスと`.`・`..`を解決します。
HTTPSの443番とHTTPの80番は省略表記と同じものとして比べ、URLと設置先の日本語パスも同じ形へそろえます。
JSONをPHPの連想配列へ変換すると数値キーのobjectがlistになるため、保存APIとZIPでJSON本来の型も確認します。
元のプレビュー保存先には変更を加えず、別の保存先でブラウザ検証と独立監査を実施します。

## 2026-10-07：音楽・SNSの埋め込みと公開URLのサンプル

Amazon MusicとSoundCloudに続き、Bandcamp、TikTok、Bluesky、Mastodon、Mixcloud、Audiomack、Facebook、Threads、Vimeo、Twitchを追加しました。
YouTube Musicは、既存のYouTubeへの変換を使います。文の途中にあるURLは普通のリンクにします。
追加サービスには、元の投稿を開くリンクも付けています。

Bandcampの曲ID、BlueskyのアカウントID、Mastodonの公式埋め込み先は、保存やプレビューで確認します。
ブログカードの取得処理と、公開アドレス、TLS、容量、転送回数、時間の制限を共用します。
確認結果は`data/log/embeds/`へ保存し、読者への表示では外部情報を取得しません。
Mastodonは外部のHTMLを貼らず、元の投稿と一致する埋め込みURLだけを、同じサイト内の`embed.php`から表示します。
この枠だけで相手のサーバーを許可するので、LOG全体のCSPに任意のドメインを加えずに済みます。

Mixcloudは現在の公式プレーヤーの配信元、`player-widget.mixcloud.com`を使います。
BlueskyとThreadsの高さ変更は、公式の配信元と、その枠からの通知かを確認します。
Facebookは公式枠の最小幅を保ち、スマホでは表示だけを縮めて文章が切れないようにします。
管理画面では同じ表示調整を使い、外部のスクリプトを読み込みません。
Twitchの設置先ドメインは実際のホスト名から指定し、400×300pxに収まらないスマホではリンクを表示します。

確認用URLを`dev/embed_samples.json`へまとめ、ローカルの確認用LOGに14種類のサンプルを作りました。
公式アカウント、公式アーティスト、公式資料が紹介する公開投稿や作品を使っています。
実画面で14種類の内容を確認し、390px幅での横はみ出し、Facebookの幅、Blueskyの高さ変更を確認しました。
HTTPによる埋め込みテストは124件、LOGの基本テストは204件とも通っています。
JavaScriptの検証では、表示調整と、配信元・通知元・高さの値を確認しています。
基本テストの検索確認は、同じ秒の投稿IDが別のIDの先頭部分と一致しないよう、完全なリンクで比べる形に直しました。
