リクエスト詳細
🐛 バグ報告
対応完了
対象アプリ: 葬儀・法事マナー完全ガイド ReigiNavi
チェックリストのLocalStorage復元時に全項目がチェック済みになるバグ
## 1. 不具合の内容
`pages/checklist.php` の `loadChecklist()` 関数において、LocalStorageから復元する際の条件判定が不正確で、保存値が `false` の項目も「チェック済み」として扱われる可能性がある。
具体的には以下のコード:
```js
if (saved[i]) {
el.classList.add('checked');
el.querySelector('.fa-check').style.display = 'block';
}
```
`saved[i]` は `saveChecklist()` で `true` または `false` のBoolean値として保存されるが、`JSON.parse` 後に `saved[i]` を truthy 評価すると `false` は正しくスキップされる。一見問題ないように見えるが、`saveChecklist()` が `state[i] = el.classList.contains('checked')` でインデックスをキーとして保存するため、**チェックリストの項目が将来追加・削除された場合にインデックスがずれ**、別の項目のチェック状態が復元されてしまう。
さらに重大な問題として、`loadChecklist()` は `DOMContentLoaded` 内で `loadChecklist('participant')` と `loadChecklist('chief')` の両方を呼び出しているが、**`pane-chief` は初期状態で `display:none`** に相当する非activeクラスのため、`getItems('chief')` が返すノードリストは正しく取得できるものの、`pane-participant` のタブ切り替え後に `chief` タブを初めて開いたとき、`DOMContentLoaded` 時点での `getItems` 呼び出しは問題ないとしても、`resetChecklist` 後に `updateProgress` を呼ぶと `progress-fill-chief` および `progress-text-chief` 要素への参照は問題ないが、`loadChecklist` が `DOMContentLoaded` 完了前にアイテム数 `0/0` と誤表示するリスクがある(PHPで動的生成されるためタイミング依存)。
## 2. 根拠・発生しそうな条件
- `saveChecklist()` がインデックス(0, 1, 2…)をキーにしているため、PHPの `$participant_items` や `$chief_items` 配列に項目が1件でも追加・削除されると、保存済みのLocalStorageデータと項目の対応がズレる。
- 例:v1.0.0 でチェックリストを使ったユーザーがv1.2.0にアクセスすると、古いインデックスのチェック状態が別の項目に適用される。
- `saved[i]` の判定で `false` を明示的に除外していないため、将来的に保存形式が変わった場合(例:`localStorage.getItem` が `null` を返しJSONパース失敗時のフォールバックが `{}`)に挙動が不安定になる。
## 3. 期待動作
- LocalStorageに保存されたチェック状態が、項目の文字列(テキスト)をキーとして保存・復元される。
- 項目が追加・変更されても既存のチェック状態が誤って別項目に引き継がれない。
- `saved[i]` の評価は `=== true` で厳密比較する。
## 4. 修正方針
### `saveChecklist()` の修正
```js
function saveChecklist(type) {
var key = LS_KEYS[type];
var state = {};
getItems(type).forEach(function(el) {
// インデックスではなく項目テキストをキーにする
var label = el.querySelector('.chk-label').textContent.trim();
state[label] = el.classList.contains('checked');
});
localStorage.setItem(key, JSON.stringify(state));
}
```
### `loadChecklist()` の修正
```js
function loadChecklist(type) {
var key = LS_KEYS[type];
var saved = {};
try { saved = JSON.parse(localStorage.getItem(key) || '{}'); } catch(e){}
var items = getItems(type);
items.forEach(function(el) {
var label = el.querySelector('.chk-label').textContent.trim();
if (saved[label] === true) {
el.classList.add('checked');
el.querySelector('.fa-check').style.display = 'block';
}
});
updateProgress(type);
}
```
### 後方互換
- 既存ユーザーの旧インデックス形式のデータは読み込み時に自動的に無視される(テキストキーが存在しないため `undefined`、`=== true` を満たさない)。チェックがリセットされる形になるが、誤ったチェック状態を引き継ぐよりも安全。
- PHPテンプレート側(`checklist.php`)の変更は不要。
`pages/checklist.php` の `loadChecklist()` 関数において、LocalStorageから復元する際の条件判定が不正確で、保存値が `false` の項目も「チェック済み」として扱われる可能性がある。
具体的には以下のコード:
```js
if (saved[i]) {
el.classList.add('checked');
el.querySelector('.fa-check').style.display = 'block';
}
```
`saved[i]` は `saveChecklist()` で `true` または `false` のBoolean値として保存されるが、`JSON.parse` 後に `saved[i]` を truthy 評価すると `false` は正しくスキップされる。一見問題ないように見えるが、`saveChecklist()` が `state[i] = el.classList.contains('checked')` でインデックスをキーとして保存するため、**チェックリストの項目が将来追加・削除された場合にインデックスがずれ**、別の項目のチェック状態が復元されてしまう。
さらに重大な問題として、`loadChecklist()` は `DOMContentLoaded` 内で `loadChecklist('participant')` と `loadChecklist('chief')` の両方を呼び出しているが、**`pane-chief` は初期状態で `display:none`** に相当する非activeクラスのため、`getItems('chief')` が返すノードリストは正しく取得できるものの、`pane-participant` のタブ切り替え後に `chief` タブを初めて開いたとき、`DOMContentLoaded` 時点での `getItems` 呼び出しは問題ないとしても、`resetChecklist` 後に `updateProgress` を呼ぶと `progress-fill-chief` および `progress-text-chief` 要素への参照は問題ないが、`loadChecklist` が `DOMContentLoaded` 完了前にアイテム数 `0/0` と誤表示するリスクがある(PHPで動的生成されるためタイミング依存)。
## 2. 根拠・発生しそうな条件
- `saveChecklist()` がインデックス(0, 1, 2…)をキーにしているため、PHPの `$participant_items` や `$chief_items` 配列に項目が1件でも追加・削除されると、保存済みのLocalStorageデータと項目の対応がズレる。
- 例:v1.0.0 でチェックリストを使ったユーザーがv1.2.0にアクセスすると、古いインデックスのチェック状態が別の項目に適用される。
- `saved[i]` の判定で `false` を明示的に除外していないため、将来的に保存形式が変わった場合(例:`localStorage.getItem` が `null` を返しJSONパース失敗時のフォールバックが `{}`)に挙動が不安定になる。
## 3. 期待動作
- LocalStorageに保存されたチェック状態が、項目の文字列(テキスト)をキーとして保存・復元される。
- 項目が追加・変更されても既存のチェック状態が誤って別項目に引き継がれない。
- `saved[i]` の評価は `=== true` で厳密比較する。
## 4. 修正方針
### `saveChecklist()` の修正
```js
function saveChecklist(type) {
var key = LS_KEYS[type];
var state = {};
getItems(type).forEach(function(el) {
// インデックスではなく項目テキストをキーにする
var label = el.querySelector('.chk-label').textContent.trim();
state[label] = el.classList.contains('checked');
});
localStorage.setItem(key, JSON.stringify(state));
}
```
### `loadChecklist()` の修正
```js
function loadChecklist(type) {
var key = LS_KEYS[type];
var saved = {};
try { saved = JSON.parse(localStorage.getItem(key) || '{}'); } catch(e){}
var items = getItems(type);
items.forEach(function(el) {
var label = el.querySelector('.chk-label').textContent.trim();
if (saved[label] === true) {
el.classList.add('checked');
el.querySelector('.fa-check').style.display = 'block';
}
});
updateProgress(type);
}
```
### 後方互換
- 既存ユーザーの旧インデックス形式のデータは読み込み時に自動的に無視される(テキストキーが存在しないため `undefined`、`=== true` を満たさない)。チェックがリセットされる形になるが、誤ったチェック状態を引き継ぐよりも安全。
- PHPテンプレート側(`checklist.php`)の変更は不要。
💬 返信 (3)
🛠 開発を開始しました (バグ修正 (reiginavi))
ご要望ありがとうございます。AI 開発ワーカーが実装を開始します。
通常 5〜30 分で Pull Request を作成し、レビュー後にリリースされます。
ご要望ありがとうございます。AI 開発ワーカーが実装を開始します。
通常 5〜30 分で Pull Request を作成し、レビュー後にリリースされます。
📝 開発が完了しました
ご要望いただいた内容の実装が完了し、最終チェック段階に入りました。
レビュー (自動) → リリース、の流れで進みます。
もう少々お待ちください。
ご要望いただいた内容の実装が完了し、最終チェック段階に入りました。
レビュー (自動) → リリース、の流れで進みます。
もう少々お待ちください。
✅ リリース完了のお知らせ
ご要望いただいた「葬儀・法事マナー完全ガイド ReigiNavi」を実装し、リリースいたしました。
【ご利用方法】
ダッシュボード: https://www.aiapps.jp/?action=dashboard
アプリ詳細: https://www.aiapps.jp/apps/show.php?slug=reiginavi
デモ環境は 1 時間以内に自動構築されます:
https://www.aiapps.jp/demo/reiginavi/
ご利用ありがとうございます!
ご要望いただいた「葬儀・法事マナー完全ガイド ReigiNavi」を実装し、リリースいたしました。
【ご利用方法】
ダッシュボード: https://www.aiapps.jp/?action=dashboard
アプリ詳細: https://www.aiapps.jp/apps/show.php?slug=reiginavi
デモ環境は 1 時間以内に自動構築されます:
https://www.aiapps.jp/demo/reiginavi/
ご利用ありがとうございます!
Echo
Iris