# 06 設定・保存設計 ## 設定方式 利用者が編集する設定はUTF-8のTOMLとし、`config.toml`一つを入口にする。初期設定の全項目を記載しなくても、差分だけで上書きできる。`schema_version = 1`で構造の版を識別する。設定から任意のコードやシェルを実行しない。 優先順位は「組込み既定値 < 利用者設定 < 起動引数で明示した一時上書き」。`--config PATH`は読む利用者設定ファイルを置き換え、二つの利用者設定を暗黙に合成しない。起動引数はファイルパス、設定パス、初期ページ等の限定された項目だけを許可する。読書状態は表示位置の復元にのみ使い、キーや配色の設定より優先させない。 文書と同じディレクトリにある設定やZIP内部の設定は自動読込みしない。ファイルを開くだけでキーや許可範囲が変更されることを防ぐ。 ## OSごとの配置 | 種類 | Linux | Windows | |---|---|---| | 設定 | `$XDG_CONFIG_HOME/docview/config.toml` | `%APPDATA%\DocView\config.toml` | | 読書状態・履歴 | `$XDG_STATE_HOME/docview/state.json` | `%LOCALAPPDATA%\DocView\state\state.json` | | キャッシュ | `$XDG_CACHE_HOME/docview/` | `%LOCALAPPDATA%\DocView\cache\` | | ログ | `$XDG_STATE_HOME/docview/logs/` | `%LOCALAPPDATA%\DocView\logs\` | | ZIP等の作業領域 | cache配下の`sessions//` | cache配下の`sessions\\` | Linuxの未設定/空のXDG変数は順に`~/.config`、`~/.local/state`、`~/.cache`へフォールバックする。相対パスのXDG値は仕様に従い無効とする。Windowsは文字列置換した環境変数だけに頼らず、Known Folders APIでRoamingAppDataとLocalAppDataを解決する。QtのQStandardPathsは共通の経路解決を補助するが、AppConfigLocationのWindows既定はLocal側であるため、上記Roaming側設定と混同しない。[XDG仕様](https://specifications.freedesktop.org/basedir/latest/)、[QStandardPaths](https://doc.qt.io/qt-6/qstandardpaths.html)、[Windows Known Folders](https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid)を参照。 すべてユーザー専用領域とし、Linuxではディレクトリ0700・状態ファイル0600相当、Windowsでは現在ユーザー中心のACLを継承・確認する。インストール先、実行ファイル隣、現在ディレクトリへ暗黙に設定を書かない。 ## 設定項目 数値は初期提案であり、08の性能測定に合わせて調整する。セキュリティの絶対上限・隔離の有効化は利用者設定で解除できない。 | キー | 型・既定値 | 範囲/効果 | |---|---|---| | schema_version | integer = 1 | 未知の将来版は適用しない | | ui.theme | string = "dark" | dark / light / system | | ui.status_bar | boolean = true | 状態1行を表示。zsで一時切替 | | ui.toc_visible | boolean = false | 初期の目次表示 | | ui.pages_visible | boolean = false | 初期のページ/章一覧表示 | | ui.font_size | number = 14 | 10〜28論理px、補助UIの文字サイズ | | ui.panel_width | integer = 280 | 160〜600論理px、画面幅でも上限を制約 | | pdf.zoom_mode | string = "fit-width" | fit-width / fit-page / percent | | pdf.zoom_percent | integer = 100 | 25〜800、percent時のみ使用 | | pdf.preserve_colors | boolean = true | 本文の原色を維持。falseは表示フィルターを使う追加提案 | | reading.font_size | number = 18 | 12〜40論理px、流し込みHTML/EPUBの利用者表示設定 | | reading.line_height | number = 1.6 | 1.0〜2.5。著者指定維持との切替を持つ | | reading.use_publisher_style | boolean = true | 既定は著者スタイル。利用者上書き時もwriting-mode等を保持 | | keys.sequence_timeout_ms | integer = 800 | 0(無期限)または200〜3000、複数打鍵の待機。IME合成中は解釈しない | | performance.tile_cache_mib | integer = 256 | 64〜512、PDFタイル予算。総RSS制限ではない | | performance.prefetch_pages | integer = 1 | 0〜3、前後ページ。メモリ圧迫時は自動抑制 | | privacy.remember_position | boolean = true | 読書位置を保存する提案機能 | | privacy.recent_documents | integer = 20 | 0〜100、0で最近使った一覧を無効化 | | privacy.store_text_anchor | boolean = false | 本文断片の永続保存。無効でもCFI/fragment等で復元可能 | `pdf.preserve_colors=false`の表示フィルターは原色を変えるため、正確な再現性の受入はtrueで行う。初期実装でフィルターを採用しない場合、falseを未対応として検証エラーにし、黙って受け付けない。 ## 設定例 以下は設計上の設定例であり、アプリケーションのプログラムではない。通常は変更した項目だけを記述する。 ```toml schema_version = 1 [ui] theme = "dark" status_bar = true toc_visible = false pages_visible = false font_size = 14 panel_width = 280 [pdf] zoom_mode = "fit-width" zoom_percent = 100 preserve_colors = true [reading] use_publisher_style = true font_size = 18 line_height = 1.6 [keys] sequence_timeout_ms = 800 [performance] tile_cache_mib = 256 prefetch_pages = 1 [privacy] remember_position = true recent_documents = 20 store_text_anchor = false [[keybindings]] mode = "normal" context = "content" keys = ["j"] command = "scroll.down" [[keybindings]] mode = "normal" context = "global" keys = ["t"] command = "panel.toc.toggle" [[keybindings]] mode = "normal" context = "content" keys = ["]", "]"] command = "nav.heading_next" ``` ## キー設定の仕様 1. 一つのキーは`j`、`J`、`Ctrl+D`、`Alt+Left`、`Esc`のような標準表記とする。大文字と小文字を区別し、印字キーはキーボード配列上の文字として解釈する。物理スキャンコードへの依存は初期版で導入しない。 2. `keys`配列は順番に押すキーを表す。`["g", "g"]`、`["Ctrl+W", "w"]`のように記述する。修飾キーの同時押しは一要素に含める。 3. `mode`はnormal / command / search / dialogとし、入力欄で合成中のIMEイベントは対応付けより優先する。`context`はglobal / content / toc / pagesとする。commandとsearchの文字入力はユーザーキー設定で奪わない。 4. 適用キーは(mode, context, keys)で同定する。利用者設定の同じ組は既定定義を一つ置換する。既定を無効化する場合は`command = "unbound"`とする。同じファイルに同じ組が二つあれば検証エラーとする。 5. 現在のパネルcontextを先に、globalを次に評価する。通常モードのtはどのパネルからも目次を切り替えるが、文字入力中のtは入力文字になる。 6. `g`と`gg`のように実行コマンドが互いに前方一致する定義は、同時に有効になるcontext間も含めて拒否する。既定のgは未実行の接頭辞なのでggと共存できる。部分列の待機中に不一致のキーが来たら接頭辞を破棄し、新しいキーを一度だけ再評価する。ただし数字Gの入力中は不一致キーも消費し、意図しない操作を起こさない。 7. Escは複数打鍵・検索・コマンド・一時モードを抜ける回復手段として予約し、再割当て不可とする。OSが予約するキーやファイル選択ダイアログの標準操作はアプリが横取りしない。 8. コマンドID、引数、必要能力を検証する。存在しないIDは保存成功として扱わない。現在の文書で能力がない操作は理由を短く表示する。 数値付きページ移動`42G`はInput Routerの限定文法として扱い、数字全般の自由なキー再割当てとは衝突させない。詳細なキー一覧・文法・フォーカス動作は04を正とする。 ## 再読込みとエラー 設定は起動時と`:reload`時に読み込む。初期版はファイル監視による自動適用を行わず、保存途中の内容による揺れを防ぐ。最大設定サイズは1 MiB、入れ子深度やキー数にも上限を設ける。 読込み→TOML解析→型と値域→キー衝突→コマンド能力→適用計画の順に検証する。全検証が成功した場合だけ設定スナップショットを一括交換する。失敗した場合は直前の有効設定を保持し、行番号・キー・修正理由を表示する。初回起動なら組込み既定値を使う。 未知のキーは綴り間違いを見逃さないためエラーとする。将来版のschema_versionは読取り専用の説明を表示し、利用者ファイルを自動で書き換えない。移行が必要な場合は変換案とバックアップを用意し、明示操作で保存する。 配色・パネル・キー・キャッシュ予算は即時反映し、再配置を伴う文字サイズは現在の論理位置を保持して反映する。適用中はキー列を取消し、二重実行を防ぐ。再起動が必要な項目を将来追加するときは、効果発生時点をスキーマに明示する。 ## 読書状態と履歴 状態は`state.json`へ保存し、`state_version`、文書ID、ファイル識別情報、最終位置、倍率、更新時刻を持つ。アプリ設定は自動で書き換えない。パネルの一時切替はセッション内状態であり、次回起動はconfigの既定へ戻す。 元ファイルの完全ハッシュは初期表示を妨げない低優先処理にする。最初は正規化したパス・サイズ・更新時刻・利用可能なファイルIDで候補を探す。文書の同一性が確認できない場合は「以前の位置を復元できない」とし、似たファイルへ位置を自動転用しない。ファイルが変更された場合は保存アンカーを再検証し、曖昧なら位置選択を提示する。 位置は停止後2秒のdebounceと最大10秒間隔を提案し、正常終了時にも保存する。急な終了では直前10秒程度の位置更新を失う可能性がある。保存先と同じディレクトリ内の一時ファイルへ書き、flush後に原子的な置換を使う。前回正常版を1世代だけ残し、破損時はバックアップへ戻す。 アプリはユーザーごとに一つの状態Writerを持つ。二重起動は既存インスタンスへファイルオープン要求を送る。IPCは同じユーザーのプロセスだけに制限する。ロックが得られない場合は読書専用モードで起動し、設定・状態を競合上書きしない。 `:history clear`は最近使った一覧、保存位置、textQuote、状態バックアップを消去する提案コマンドとする。実行前に対象件数を表示し、文書原本は含めない。`privacy.remember_position=false`は将来の保存を止める設定であり、既存データの消去とは分ける。 ## 一時データとログ ZIP/EPUB作業領域はsessionIdごとに分離し、正常終了で削除する。起動時は自分の未使用セッション領域だけを回収し、他インスタンスが使用中の領域を削除しない。削除対象の実体・所有者・ロックを確認し、シンボリックリンクを追跡しない。 ログは日時、エラーコード、形式、処理時間、匿名の要求IDを中心とし、原則として本文・パスワード・完全パスを記録しない。ローカルに5 MiB×3世代を提案する。外部送信は行わない。詳細診断を保存する機能を将来設ける場合も、利用者が内容を確認して書き出す方式とする。 キャッシュは削除しても原本文書と設定に影響しない。履歴や設定の破損で本文が開けなくならないよう、保存処理のエラーは表示処理から切り離す。