summaryrefslogtreecommitdiff
path: root/doc/Design.md
diff options
context:
space:
mode:
Diffstat (limited to 'doc/Design.md')
-rw-r--r--doc/Design.md871
1 files changed, 871 insertions, 0 deletions
diff --git a/doc/Design.md b/doc/Design.md
new file mode 100644
index 0000000..20de3e5
--- /dev/null
+++ b/doc/Design.md
@@ -0,0 +1,871 @@
+# Design.md
+
+## 1. 設計概要
+
+本拡張は、Webページとの接点をContent Scriptに限定し、権限を伴うOllama通信と設定管理をBackgroundへ分離する。
+
+```text
+Web Page
+ │
+ │ selection
+ ▼
+Content Script
+ │ runtime.Port
+ ▼
+Background
+ │ fetch / HTTP
+ ▼
+Ollama
+ │ streaming
+ ▼
+Background
+ │ runtime.Port
+ ▼
+Content Script
+ │
+ ▼
+Shadow DOM Popover
+```
+
+設計原則:
+- ページ由来データは信頼しない。
+- Content Scriptは最小責務とする。
+- Ollama通信はBackgroundのみ(Ollama REST API + Fetch API + NDJSON streaming)。
+- UI文字列はHTMLとして評価しない。
+- 翻訳要求は常に1-shot。
+- 同時翻訳は1件に制限。
+- 設定値はUIだけでなくBackgroundでも検証する。
+- Ollama接続先は初期版ではlocalhost / 127.0.0.1 + 任意ポートのみ許可する。
+
+---
+
+## 2. コンポーネント構成
+
+| コンポーネント | 主責務 |
+|---|---|
+| `content/selection-controller` | Selection検出、Range位置取得 |
+| `content/action-button` | 翻訳アイコン表示/非表示 |
+| `content/translation-popover` | 翻訳結果UI、stream描画、copy/regenerate/close |
+| `content/port-client` | Backgroundとの長期接続 |
+| `background/message-router` | Content Scriptからのrequest受信・validation |
+| `background/translation-session` | requestId、AbortController、active stream管理 |
+| `background/ollama-client` | Ollama REST API呼び出し(fetch + NDJSON streaming) |
+| `background/settings-service` | storage読書き、default、validation |
+| `popup` | 有効/無効、簡易設定、Options起動 |
+| `options` | 全設定UI |
+| `shared/settings-schema` | 設定型、既定値、範囲 |
+| `shared/messages` | runtime message型定義 |
+| `shared/constants` | 最大選択長等 |
+
+---
+
+## 3. 推奨ディレクトリ構成
+
+```text
+/
+├─ manifest.json
+├─ package.json
+├─ src/
+│ ├─ background/
+│ │ ├─ index.js
+│ │ ├─ message-router.js
+│ │ ├─ translation-session.js
+│ │ ├─ ollama-client.js
+│ │ └─ settings-service.js
+│ ├─ content/
+│ │ ├─ index.js
+│ │ ├─ selection-controller.js
+│ │ ├─ action-button.js
+│ │ ├─ translation-popover.js
+│ │ ├─ port-client.js
+│ │ └─ content.css
+│ ├─ popup/
+│ │ ├─ popup.html
+│ │ ├─ popup.js
+│ │ └─ popup.css
+│ ├─ options/
+│ │ ├─ options.html
+│ │ ├─ options.js
+│ │ └─ options.css
+│ └─ shared/
+│ ├─ settings-schema.js
+│ ├─ messages.js
+│ ├─ prompt.js
+│ └─ constants.js
+├─ tests/
+│ ├─ unit/
+│ └─ integration/
+└─ docs/
+ ├─ Specifications.md
+ └─ Design.md
+```
+
+実際のビルド方式に応じて配置は調整してよいが、責務分離は維持する。
+
+---
+
+## 4. Manifest設計
+
+概念例:
+
+```json
+{
+ "manifest_version": 3,
+ "name": "Selected Text Translator",
+ "version": "0.1.0",
+ "permissions": ["storage"],
+ "host_permissions": ["http://localhost/*", "http://127.0.0.1/*"],
+ "background": {
+ "scripts": ["background.js"],
+ "type": "module"
+ },
+ "content_scripts": [
+ {
+ "matches": ["http://*/*", "https://*/*"],
+ "js": ["content.js"],
+ "css": ["content.css"],
+ "run_at": "document_idle"
+ }
+ ],
+ "action": {
+ "default_popup": "popup.html"
+ },
+ "options_ui": {
+ "page": "options.html",
+ "open_in_tab": true
+ }
+}
+```
+
+### 4.1 Manifest実装ルール
+- `background.service_worker` は使用しない。
+- `browser_action` は使用しない。
+- `scripting` 権限は初期版では付与しない。
+- `tabs` 権限は付与しない。
+- localhost以外のhost permissionは初期版では付与しない。
+- Firefoxのmatch patternはポート番号指定に対応していない(Firefox bug 1362809 / 1468162)。`http://localhost/*` および `http://127.0.0.1/*` は任意ポートに一致するため、実際に通信してよい接続先はアプリ側Host validatorで `http://localhost:<port>` / `http://127.0.0.1:<port>` のみに限定する。
+
+---
+
+## 5. Content Script設計
+
+### 5.1 Selection取得
+
+標準DOM APIを使用する。
+
+```js
+const selection = window.getSelection();
+const text = selection?.toString().trim();
+
+if (!text) return;
+
+const range = selection.getRangeAt(0);
+const rect = range.getBoundingClientRect();
+```
+
+### 5.2 検出イベント
+初期実装候補:
+- `selectionchange`: 選択状態更新用
+- `mouseup`: 選択確定後の表示タイミング用
+- `keyup`: Shift + Arrow等のキーボード選択対応用
+
+過剰発火を避けるため、UI更新には軽いdebounceを適用してよい。
+
+### 5.3 Action Button表示条件
+
+```text
+selection exists
+AND trimmed text length > 0
+AND text length <= MAX_SELECTION_LENGTH
+AND extension enabled
+```
+
+### 5.4 位置計算
+- `Range.getBoundingClientRect()` を基本とする。
+- 選択が複数行の場合でも最後/全体矩形を基準に配置する。
+- viewport端ではポップオーバーが画面外へ出ないよう補正する。
+- scroll/resize時は再配置または非表示化する。
+
+---
+
+## 6. UI分離設計
+
+### 6.1 Shadow DOM
+
+ページCSSとの衝突を避けるため、Content Scriptが専用host要素を追加し、その内部にShadow DOMを作る。
+
+```js
+const host = document.createElement('div');
+document.documentElement.append(host);
+const shadow = host.attachShadow({ mode: 'open' });
+```
+
+`open/closed` は実装都合で選択可能だが、セキュリティ境界として依存しない。
+
+### 6.2 DOM挿入ルール
+
+禁止:
+```js
+element.innerHTML = translatedText;
+shadowRoot.innerHTML = translatedText;
+```
+
+許可:
+```js
+element.textContent = translatedText;
+```
+
+静的テンプレートHTMLを自前固定文字列として構築する場合も、外部入力を文字列連結しない。
+
+---
+
+## 7. 翻訳ポップオーバー設計
+
+### 7.1 状態
+
+```text
+HIDDEN
+READY
+LOADING
+STREAMING
+COMPLETED
+ERROR
+```
+
+### 7.2 表示内容
+
+| 要素 | 内容 |
+|---|---|
+| Header | 翻訳結果 |
+| Meta | Model名 / Ollama |
+| Body | streaming翻訳本文 |
+| Copy | 完了/途中結果をclipboardへコピー |
+| Regenerate | 同じ選択文字列で再要求 |
+| Close | ポップオーバー非表示 |
+
+### 7.3 UI挙動
+- Icon押下直後にLOADING表示。
+- 最初のchunk受信後STREAMINGへ遷移。
+- 完了イベントでCOMPLETED。
+- Abortされた旧requestのchunkはrequestId不一致なら無視。
+- Error時は再生成可能な状態に戻す。
+
+---
+
+## 8. Runtime通信設計
+
+Streamingには `browser.runtime.connect()` / `runtime.Port` を使用する。
+
+### 8.1 Port名
+
+```text
+translation-stream
+```
+
+### 8.2 Content → Background message
+
+#### translate
+
+```js
+{
+ type: 'translate',
+ requestId: 'uuid-or-random-id',
+ text: 'selected text'
+}
+```
+
+#### cancel
+
+```js
+{
+ type: 'cancel',
+ requestId: '...'
+}
+```
+
+### 8.3 Background → Content message
+
+#### started
+
+```js
+{
+ type: 'started',
+ requestId: '...',
+ model: '...'
+}
+```
+
+#### chunk
+
+```js
+{
+ type: 'chunk',
+ requestId: '...',
+ text: 'partial translation'
+}
+```
+
+#### completed
+
+```js
+{
+ type: 'completed',
+ requestId: '...'
+}
+```
+
+#### error
+
+```js
+{
+ type: 'error',
+ requestId: '...',
+ code: 'OLLAMA_UNAVAILABLE',
+ message: '...'
+}
+```
+
+### 8.4 Validation
+Backgroundは最低限以下を確認する。
+- `type` が既知値か
+- `requestId` がstringか
+- `text` がstringか
+- text lengthが上限以内か
+- 現在のsender/portがContent Script由来か
+
+---
+
+## 9. TranslationSession設計
+
+Backgroundではactive translationを原則1件だけ保持する。
+
+```js
+class TranslationSession {
+ requestId;
+ abortController;
+ sourceText;
+ startedAt;
+}
+```
+
+新しい翻訳要求:
+
+```text
+translate(request B)
+ ↓
+active request Aあり?
+ ├─ yes → Abort A
+ └─ no
+ ↓
+request Bをactiveに設定
+ ↓
+Ollama streaming開始
+```
+
+### 9.1 古いchunk対策
+受信chunkごとにactive requestIdを確認し、不一致なら破棄する。
+
+---
+
+## 10. OllamaClient設計
+
+### 10.1 API
+
+Ollama REST APIをFetch APIで直接呼び出す。`ollama-js`等のサードパーティSDKは使用しない。
+
+概念例:
+
+```js
+const response = await fetch(`${host}/api/chat`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ model: settings.model,
+ messages: [
+ {
+ role: 'system',
+ content: buildSystemPrompt(settings)
+ },
+ {
+ role: 'user',
+ content: selectedText
+ }
+ ],
+ stream: true,
+ think: settings.thinking, // off時は省略
+ options: buildOptions(settings)
+ }),
+ signal: abortController.signal
+});
+
+// NDJSONストリームをバッファリングしつつ行単位でパースする
+const reader = response.body.getReader();
+const decoder = new TextDecoder();
+let buffer = '';
+for (;;) {
+ const { done, value } = await reader.read();
+ if (done) break;
+ buffer += decoder.decode(value, { stream: true });
+ // bufferを行単位で分割し、完全な行のみJSON.parseする
+ // 末尾の不完全行は次回チャンクに持ち越す
+}
+```
+
+### 10.2 System Prompt構築
+
+```text
+BUILTIN_SYSTEM_PROMPT
+
+[User translation preferences]
+USER_SYSTEM_PROMPT
+```
+
+固定promptは常に先頭に置く。
+
+### 10.3 buildOptions
+未設定値はrequest objectへ含めない。
+
+例:
+
+```js
+{
+ temperature: 0.2,
+ num_ctx: 16384,
+ seed: 42,
+ top_k: 40,
+ top_p: 0.9,
+ min_p: 0.05,
+ typical_p: 1.0,
+ repeat_last_n: 64,
+ repeat_penalty: 1.1,
+ presence_penalty: 0.0,
+ frequency_penalty: 0.0,
+ num_keep: 4,
+ num_predict: -1,
+ stop: ['<END>']
+}
+```
+
+Seed可変時は`seed`をoptionsへ含めない設計を優先する。
+
+---
+
+## 11. 設定スキーマ
+
+推奨形:
+
+```js
+{
+ enabled: true,
+ ollamaHost: 'http://localhost:11434',
+ model: '',
+ targetLanguage: 'ja',
+ thinking: false,
+ temperature: 0.2,
+ contextLength: 16384,
+ seedMode: 'random',
+ seedValue: null,
+ userSystemPrompt: '',
+ advanced: {
+ numKeep: null,
+ numPredict: null,
+ topK: null,
+ topP: null,
+ minP: null,
+ typicalP: null,
+ repeatLastN: null,
+ repeatPenalty: null,
+ presencePenalty: null,
+ frequencyPenalty: null,
+ stop: []
+ }
+}
+```
+
+---
+
+## 12. 設定値validation
+
+### 12.1 Basic
+
+| enabled | boolean |
+| ollamaHost | URL形式。**`http://localhost:<port>` または `http://127.0.0.1:<port>` のみ許可(schemeはhttp固定、外部ホスト・LAN IP・httpsは拒否)。必須制限** |
+| model | non-empty string |
+| targetLanguage | 定義済み値 |
+| thinking | `false/true/low/medium/high` 相当 |
+| temperature | 0.0〜2.0、0.1刻み |
+| contextLength | `4096/8192/16384/32768/131072/262144` のいずれか |
+| seedMode | `random/fixed` |
+| seedValue | fixed時のみinteger |
+| userSystemPrompt | string、最大長を設定 |
+
+### 12.2 Advanced
+
+推奨UI step:
+
+| 項目 | step | validation方針 |
+|---|---:|---|
+| num_keep | 1 | integer |
+| num_predict | 1 | integer、-1許容 |
+| top_k | 1 | integer、0以上 |
+| top_p | 0.05 | float |
+| min_p | 0.01 | float |
+| typical_p | 0.05 | float |
+| repeat_last_n | 1 | integer、-1許容 |
+| repeat_penalty | 0.05 | float |
+| presence_penalty | 0.05 | float |
+| frequency_penalty | 0.05 | float |
+| stop | - | string[] |
+
+Ollamaが明示的な最小/最大値を保証していない項目について、UI側で過度に狭い範囲を独自設定しない。型と明らかな不正値を中心に検証する。
+
+---
+
+## 13. Context Length UI設計
+
+固定プリセット:
+
+```js
+const CONTEXT_PRESETS = [
+ 4096,
+ 8192,
+ 16384,
+ 32768,
+ 131072,
+ 262144
+];
+```
+
+UI表示:
+
+```text
+4k ─ 8k ─ 16k ─ 32k ─ 128k ─ 256k
+```
+
+実装上はrange inputのindex値を使用してよい。
+
+```text
+0 => 4096
+1 => 8192
+2 => 16384
+3 => 32768
+4 => 131072
+5 => 262144
+```
+
+モデル自身が対応する最大contextを超える場合、Ollama側でエラーまたは制約される可能性があるため、そのエラーをUIへ返す。
+
+---
+
+## 14. Temperature UI設計
+
+```text
+min=0.0
+max=2.0
+step=0.1
+```
+
+スライダー値は表示用数値と同期する。
+
+```html
+<input type="range" min="0" max="2" step="0.1">
+```
+
+---
+
+## 15. Seed UI設計
+
+```text
+Seed
+[ 可変 ] [ 固定 ]
+
+固定選択時:
+固定値 (整数) [ 42 ▲▼ ]
+```
+
+- random時: seed optionを送らない。
+- fixed時: `seedValue` を整数として送る。
+- Seed Valueはnumber input + spinnerでよい。
+
+---
+
+## 16. Advanced UI設計
+
+数値項目は`input type="number"`相当のUIとする。
+
+例:
+
+```text
+top_p
+[ 0.90 ][▲]
+ [▼]
+```
+
+HTML native spinnerを使用できる場合は活用し、独自実装を増やさない。
+
+stopは複数行textareaとし、1行1sequenceとして保存する。
+
+---
+
+## 17. SettingsService設計
+
+### 17.1 保存
+
+`browser.storage.local` を使用。
+
+### 17.2 読み込み
+1. default settings生成
+2. storage値取得
+3. merge
+4. validation
+5. 不正値はdefaultへフォールバック
+
+### 17.3 設定変更通知
+設定変更時に `browser.storage.onChanged` を使用し、Content Script / Background双方が必要な設定を更新できるようにする。
+
+---
+
+## 18. Security Design
+
+### 18.1 Trust Boundary
+
+```text
+UNTRUSTED
+Web Page / DOM / Selection
+ │
+ ▼
+Content Script
+ │ structured messages only
+ ▼
+PRIVILEGED
+Background / Ollama access / storage
+```
+
+### 18.2 XSS対策
+- 選択文字列をHTML化しない。
+- LLM出力をHTML化しない。
+- Markdown rendererを初期版では導入しない。
+- DOM挿入は`textContent`中心。
+
+### 18.3 Prompt Injection対策
+翻訳用途ではページ本文を命令ではなくデータとして固定system promptで扱う。
+
+ただしLLMの挙動を完全保証するセキュリティ境界としてPromptのみへ依存しない。
+
+### 18.4 Remote Code禁止
+- CDN script禁止。
+- `eval`禁止。
+- `new Function`禁止。
+- remote import禁止。
+- npm依存はbundleに含める。
+
+### 18.5 Storage
+- `storage.local` は暗号化ストレージではない前提。
+- API key等の秘密情報は初期版では扱わない。
+
+### 18.6 Message Validation
+Content側のデータをBackgroundで再検証する。
+
+### 18.7 Resource Abuse
+- MAX_SELECTION_LENGTHを設ける。
+- active translationは1件。
+- 新規requestで旧request abort。
+- 選択だけではrequestを送らない。
+
+---
+
+## 19. Error Code設計
+
+| Code | 意味 |
+|---|---|
+| `INVALID_REQUEST` | message形式不正 |
+| `TEXT_EMPTY` | 空文字 |
+| `TEXT_TOO_LONG` | 選択長超過 |
+| `OLLAMA_UNAVAILABLE` | Ollama接続不可 |
+| `MODEL_NOT_FOUND` | モデルなし |
+| `INVALID_SETTINGS` | 設定値不正 |
+| `REQUEST_ABORTED` | 新規要求等で中断 |
+| `STREAM_ERROR` | streaming中エラー |
+| `UNKNOWN_ERROR` | その他 |
+
+UIでは内部stack traceを表示しない。
+
+---
+
+## 20. Copy設計
+
+Clipboard APIのみを使用する。`document.execCommand('copy')` はdeprecatedのため使用しない。
+
+- コピーはポップオーバーのコピーボタンclick(user activation内)で発生するため、`navigator.clipboard.writeText()` をそのハンドラ内で直接呼び出す。
+- 書き込み失敗時は「コピーできませんでした」等の簡潔なメッセージをポップオーバーへ表示するのみとし、deprecated APIへフォールバックしない。
+- Firefox実機でclipboard動作を確認する。
+
+コピー対象は現在表示中の翻訳プレーンテキストのみ。
+
+---
+
+## 21. モデル一覧取得
+
+BackgroundのOllamaClientからモデル一覧取得APIを呼び、Settings UIへ返す。
+
+要件:
+- Ollama未起動時は空一覧 + 接続エラー表示。
+- 取得成功時はselect候補へ反映。
+- 保存済みmodelが一覧にない場合は警告表示。
+
+---
+
+## 22. 実装順序
+
+### Phase 1: Skeleton
+- Manifest V3
+- Background
+- Content Script
+- Popup / Options
+- build/test基盤
+
+### Phase 2: Selection UI
+- Selection検出
+- Action button
+- Shadow DOM
+- Popover skeleton
+
+### Phase 3: Messaging
+- runtime.Port
+- message schema
+- validation
+- requestId
+
+### Phase 4: Ollama
+- Ollama REST client(fetch + NDJSON streaming)
+- model list
+- chat stream
+- Abort
+
+### Phase 5: Settings
+- storage.local
+- Basic settings
+- Advanced settings
+- Context slider
+- Temperature slider
+- Seed modes
+
+### Phase 6: Security Hardening
+- textContent確認
+- CSP確認
+- message validation
+- input length limits
+- no remote code
+- permissions audit
+
+### Phase 7: Tests
+- unit
+- integration
+- Firefox実機
+
+### Phase 8: Final Verification
+- lint/test/build
+- temporary add-on install
+- acceptance criteriaを全件確認
+
+---
+
+## 23. テスト設計
+
+### 23.1 Unit
+
+| 対象 | ケース |
+|---|---|
+| settings schema | default / valid / invalid |
+| temperature | 0.1刻み / 範囲外 |
+| contextLength | presetのみ許可 |
+| seed | random / fixed / invalid integer |
+| advanced options | null除外、数値反映 |
+| prompt builder | fixed + user prompt結合 |
+| message validation | malformed message拒否 |
+| length limit | 境界値 |
+
+### 23.2 Integration
+
+Mock Ollama serverまたはmock clientを使い以下を確認。
+
+```text
+Content request
+→ Background validation
+→ Ollama stream
+→ chunk forwarding
+→ complete
+```
+
+Abortテスト:
+
+```text
+request A start
+request B start
+A abort
+B chunks only displayed
+```
+
+### 23.3 Firefox Manual/E2E
+
+対象ページ:
+- 単純HTML
+- 長文記事
+- 選択文字列にHTMLタグ表現を含むページ
+- CSSが強く指定されたページ
+- スクロール位置が深いページ
+
+確認:
+- 選択位置近傍UI
+- viewport端補正
+- scroll時挙動
+- selection解除
+- copy
+- regenerate
+- close
+- settings persistence
+- Ollama down/up
+
+---
+
+## 24. Firefox API監査チェックリスト
+
+実装完了時に以下を確認する。
+
+- [ ] Manifest V3
+- [ ] `action` 使用
+- [ ] `background.scripts` 使用
+- [ ] `background.service_worker` 不使用
+- [ ] `browser_action` 不使用
+- [ ] `tabs.executeScript()` 不使用
+- [ ] `runtime.getBackgroundPage()` 不使用
+- [ ] `window.localStorage` 不使用
+- [ ] `storage.local` 使用
+- [ ] `runtime.Port` streaming通信
+- [ ] Content ScriptからOllama fetchなし
+- [ ] `eval` / `new Function`なし
+- [ ] Remote JSなし
+- [ ] LLM出力への`innerHTML`なし
+- [ ] 不要permissionsなし
+- [ ] iframe初期対応なし
+
+---
+
+## 25. 完了時にLLMへ要求する成果物
+
+実装担当LLMは最終的に以下を提示すること。
+
+1. 実装済みソースコード一式
+2. `manifest.json`
+3. build手順
+4. Firefox一時アドオンとしてのロード手順
+5. Ollama起動/モデル準備手順
+6. 単体テスト結果
+7. 結合テスト結果
+8. Firefox実機動作確認結果
+9. 使用WebExtensions API一覧
+10. 要求permissions一覧と理由
+11. セキュリティチェック結果
+12. 未解決事項・既知制限
+
+テストを実行せず「実装したので動くはず」として完了扱いにしてはならない。実際に実行可能なテストは実行し、Firefox実機確認項目も可能な範囲で実行結果を記録する。