Skip to content

Commit 34232b4

Browse files
committed
adr
# Conflicts: # docs/adr/260614-1703-n-tool-surface-mvp-рішення.md # docs/adr/260615-2027-кастомний-вибір-bun-workspace-замість-нативного-файл-менедже.md # docs/adr/260615-2043-кастомний-вибір-bun-workspace-замість-нативного-файлового-ме.md # docs/adr/260615-2051-кастомний-вибір-bun-воркспейсу-замість-нативного-файлового-м.md
1 parent 4837d82 commit 34232b4

10 files changed

Lines changed: 449 additions & 0 deletions
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
@font-face {
2+
font-family: 'Material Symbols Outlined';
3+
font-style: normal;
4+
font-weight: 100 700;
5+
font-display: block;
6+
src:
7+
url('../../node_modules/@quasar/extras/exports/material-symbols-outlined/web-font/kJEhBvYX7BgnkSrUwT8OhrdQw4oELdPIeeII9v6oFsLjBuVY.woff2')
8+
format('woff2'),
9+
url('../../node_modules/@quasar/extras/exports/material-symbols-outlined/web-font/kJF1BvYX7BgnkSrUwT8OhrdQw4oELdPIeeII9v6oDMzByHX9rA6RzaxHMPdY43zj-jCxv3fzvRNU22ZXGJpEpjC_1v-p_4MrImHCIJIZrDCvHeel.woff')
10+
format('woff');
11+
}
12+
13+
.material-symbols-outlined {
14+
font-family: 'Material Symbols Outlined';
15+
font-weight: normal;
16+
font-style: normal;
17+
line-height: 1;
18+
letter-spacing: normal;
19+
text-transform: none;
20+
display: inline-block;
21+
white-space: nowrap;
22+
word-wrap: normal;
23+
direction: ltr;
24+
-webkit-font-smoothing: antialiased;
25+
text-rendering: optimizeLegibility;
26+
-moz-osx-font-smoothing: grayscale;
27+
font-feature-settings: 'liga';
28+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# `iconSet` Quasar — передавати об'єктом, не рядком
2+
3+
**Status:** Accepted
4+
**Date:** 2026-06-14
5+
6+
## Context and Problem Statement
7+
8+
Після додавання першого `q-select` у `CreateTaskDialog.vue` усі кнопки застосунку переставали реагувати на кліки. Налагодження показало, що `$q.iconSet.arrow.dropdown` викидає `TypeError` при рендері `q-select`: Quasar звертається до цієї властивості як до поля об'єкта, а не рядка — рядок не має такої властивості, Vue ламає цикл реактивності й усі обробники кліків губляться.
9+
10+
## Considered Options
11+
12+
- Передати `iconSet` рядком `'material-symbols-outlined'` — початковий підхід у `main.js`.
13+
- Передати `iconSet` імпортованим об'єктом з `quasar/icon-set/material-symbols-outlined.js`.
14+
15+
## Decision Outcome
16+
17+
Chosen option: "Передати `iconSet` імпортованим об'єктом", because Quasar звертається до `$q.iconSet.arrow.dropdown` (властивість об'єкта) при рендері компонентів з іконками вибору; рядок не має цієї властивості → `TypeError` → Vue ламає цикл реактивності → втрата всіх обробників кліків.
18+
19+
### Consequences
20+
21+
- Good, because після виправлення у лозі Vite зафіксовано 0 помилок після full reload; кнопки відновили реакцію на кліки.
22+
- Bad, because transcript не містить підтверджених негативних наслідків.
23+
- Neutral, because симптом виявляється лише при рендері першого `q-select` у сесії; `q-btn` самостійно баг не тригерить — він може бути прихований у проєктах без `q-select`.
24+
25+
## More Information
26+
27+
Файл: `app/src/main.js`. До виправлення: `iconSet: 'material-symbols-outlined'`. Після: `import materialSymbolsOutlined from 'quasar/icon-set/material-symbols-outlined.js'` + `iconSet: materialSymbolsOutlined`. Баг був прихований відсутністю `q-select` до появи `CreateTaskDialog`.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Локальна офлайн-модель через omlx як LLM-провайдер агента
2+
3+
**Status:** Accepted
4+
**Date:** 2026-06-14
5+
6+
## Context and Problem Statement
7+
8+
Для in-app agent-loop потрібен LLM-провайдер. Вимоги: приватність, офлайн-доступність, сумісність із наявною інфраструктурою (`tauri-plugin-http`). Обирали між хмарним Claude, локальною офлайн-моделлю та провайдер-агностичним інтерфейсом.
9+
10+
## Considered Options
11+
12+
- Claude (Anthropic) через `tauri-plugin-http` — хмарний, потребує API-ключа і мережі.
13+
- Локальна офлайн-модель через omlx (OpenAI-сумісний MLX-сервер) + Gemma 4 E4B.
14+
- Провайдер-агностичний інтерфейс із кількома бекендами — відкладено як передчасне для MVP.
15+
16+
## Decision Outcome
17+
18+
Chosen option: "Локальна офлайн-модель Gemma через omlx", because вимога — офлайн і приватність; omlx говорить OpenAI API, тому `tauri-plugin-http` підходить без змін, а формат tool-маніфесту — стандартний OpenAI function-calling із того самого `src/tool/catalog.js`. MCP залишається ціллю після MVP.
19+
20+
### Consequences
21+
22+
- Good, because end-to-end цикл підтверджено наживо: `gemma-4-e2b-it-4bit` через omlx самостійно обрала тул `create`, `dispatch` виконав через `mt-scanner`, файли `task.md` + `a.md` реально створено.
23+
- Bad, because `gemma-4-e4b-it-OptiQ-4bit` впала з HTTP 507 (memory ceiling 11.84 GB, зайнято 10.57 GB) — потрібно звільняти RAM або використовувати меншу модель.
24+
- Bad, because ~4B-модель нестабільна в генерації коректного JSON для `tool_calls` — необхідний constrained decoding як наступний крок.
25+
26+
## More Information
27+
28+
Конфіг: `OMLX_BASE_URL` (дефолт `http://127.0.0.1:8000/v1`), `OMLX_MODEL` (дефолт `gemma-4-e4b-it-OptiQ-4bit`), `OMLX_API_KEY`. Файли: `app/src/tool/llm.js` (`createOpenAiChat`, `runAgent`), `app/src/composables/use-omlx.js` (localStorage), `app/bin/task.mjs` (команда `task agent "<prompt>"`). Модель підтверджено через `/v1/models` на сервері. MCP-обгортка — ціль на потім.
29+
30+
## Update 2026-06-15
31+
32+
Додаткові деталі реалізації (сесія `e8e91f68`): `createOpenAiChat({ baseUrl, model, apiKey, fetchFn })``fetchFn` інжектується як залежність: Tauri використовує `@tauri-apps/plugin-http`, CLI — node `fetch`, тести — mock; єдиний код обслуговує всі три контексти. `runAgent({ prompt, dispatch, chat, maxSteps })` — до 3–5 кроків за прогін.
33+
34+
Підтверджено наживо: `gemma-4-e2b-it-4bit` на порту `:8000` завершила повний tool-call цикл → `mt-scanner create` → файли `demo-llm/task.md` + `demo-llm/a.md`. `gemma-4-e4b-it-OptiQ-4bit` впала з HTTP 507 (потребує 17.89 GB, ceiling 11.84 GB).
35+
36+
Рекомендація надійності: тримати малий набір тулів (≤3), по 1 за хід; наступний крок — constrained decoding для гарантованого JSON у `tool_calls`.
37+
38+
Пер-stack реалізація `n-tool-surface` у `n-tauri` (версія `1.5`): один Rust-fn на тул, `#[tauri::command]`, dispatch-invoke через camelCase-ключі, конверт `{ok,output}` / `{ok:false,error}`. Правило `n-tool-surface` авто-активується через `depInAnyPackageJson` для `vue`/`react`/`svelte`/`@tauri-apps/api`/`@capacitor/core`.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Tauri HTTP-плагін — явний wildcard-порт для localhost у capabilities
2+
3+
**Status:** Accepted
4+
**Date:** 2026-06-15
5+
6+
## Context and Problem Statement
7+
8+
Фронтенд (`AgentDialog.vue`) використовує `@tauri-apps/plugin-http` для запитів до локального LLM-сервера (`http://127.0.0.1:8000/v1/chat/completions`). Tauri відхиляв запит, попри наявний шаблон `{ "url": "http://**" }` у `capabilities/default.json`. Причина: реалізація `urlpattern` у Tauri трактує шаблон без явного порту як дозвіл лише на порт за замовчуванням (80 для `http://`) — нестандартний порт `:8000` ніколи не збігався з цим шаблоном.
9+
10+
## Considered Options
11+
12+
- Залишити `http://**` і розраховувати, що він покриє будь-який порт (не спрацьовує через семантику `urlpattern`).
13+
- Додати явні шаблони з wildcard-портом `:*` для `127.0.0.1` і `localhost`.
14+
15+
## Decision Outcome
16+
17+
Chosen option: "Явні шаблони `http://127.0.0.1:*/*` і `http://localhost:*/*`", because `http://**` не матчить нестандартний порт в реалізації `urlpattern` Tauri, а явна вказівка `:*` вирішує проблему без надмірного розширення scope.
18+
19+
### Consequences
20+
21+
- Good, because запити до `http://127.0.0.1:8000/v1/chat/completions` тепер проходять Tauri scope-перевірку; після виправлення нова помилка стала `401 API key required` від omlx-сервера — scope-проблему повністю вирішено.
22+
- Bad, because capabilities компілюються у бінарник під час build — зміна набирає чинності лише після перезапуску `tauri dev` / повного rebuild; hot-reload фронтенду недостатньо.
23+
24+
## More Information
25+
26+
Змінений файл: `app/src-tauri/capabilities/default.json` — блок `http:default``allow`, додано `{ "url": "http://127.0.0.1:*/*" }` і `{ "url": "http://localhost:*/*" }`. Існуючий `http://**` збережено для non-loopback HTTP. Компонент, що виконує запит: `app/src/components/AgentDialog.vue` (імпортує `fetch` з `@tauri-apps/plugin-http`). Перевірено: `cargo check`.
27+
28+
## Update 2026-06-15
29+
30+
Попередній фрагмент тієї самої сесії `005a85c1` (за 1 хвилину до основного запису): шаблони `{ "url": "http://127.0.0.1:*" }` і `{ "url": "http://localhost:*" }` застосовані в `capabilities/default.json`; існуючий `http://**` збережено для non-loopback HTTP. Семантика підтверджена: `urlpattern` Tauri трактує шаблон без явного порту як дозвіл лише на default-порт (80 для `http://`).
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# `~/.omlx/settings.json` як канонічне джерело конфігурації omlx у Tauri
2+
3+
**Status:** Accepted
4+
**Date:** 2026-06-15
5+
6+
## Context and Problem Statement
7+
8+
omlx API-ключ зберігався у `localStorage` браузерного webview Tauri і мав вводитися вручну через ⚙-панель `AgentDialog` для кожного застосунку окремо. CLI (`app/bin/task.mjs`) вже читав ключ із `OMLX_API_KEY` env, але Tauri-застосунок цього не робив. GUI `.app`, запущений із Finder/Dock, не бачить `~/.zshrc`, тому env-підхід вимагав `launchctl setenv` або LaunchAgent на кожній машині.
9+
10+
## Considered Options
11+
12+
- Зберігати ключ у `localStorage` — попередній підхід, ручне введення per-app.
13+
- Читати з env-змінної `OMLX_API_KEY` — проміжне рішення; не працює для GUI з Finder/Dock без додаткового налаштування.
14+
- Читати з `~/.omlx/settings.json` (файл, що вже існує на кожній машині з omlx) з env як опційним override.
15+
16+
## Decision Outcome
17+
18+
Chosen option: "Читання з `~/.omlx/settings.json` з fallback на env", because файл вже є канонічним джерелом конфігурації omlx-сервера (host, port, api_key), резолвиться через `$HOME` per-machine, не потребує додаткового launchd/plist і уніфікує джерело для UI та CLI.
19+
20+
### Consequences
21+
22+
- Good, because один файл `~/.omlx/settings.json` покриває всі машини з omlx без ручного виставлення env на кожному комп'ютері.
23+
- Good, because CLI і Tauri UI тепер читають ключ з одного джерела — `auth.api_key` із `~/.omlx/settings.json` (або `OMLX_API_KEY` env як override).
24+
- Bad, because GUI `.app` із Finder/Dock не бачить `~/.zshrc` — env-override потребує `launchctl setenv` або LaunchAgent; пряме читання файлу усуває цю проблему.
25+
26+
## More Information
27+
28+
Змінені файли:
29+
- `src-tauri/src/lib.rs` — нова Tauri-команда `omlx_config`: читає `~/.omlx/settings.json` (поля `server.host`, `server.port`, `auth.api_key`) та env-змінні `OMLX_*` як override; зареєстрована в `invoke_handler`.
30+
- `src/composables/use-omlx.js``apiKey` прибрано з `localStorage`; `loadEnv()` викликає `invoke('omlx_config')`.
31+
- `src/components/AgentDialog.vue``onMounted(loadEnv)` підтягує конфіг при відкритті діалогу.
32+
33+
Структура `~/.omlx/settings.json`: `server.host`, `server.port`, `auth.api_key`. Перевірено: `cargo check` і `vitest run` (36 тестів) — зелені.
34+
35+
## Update 2026-06-15
36+
37+
Проміжний крок тієї самої сесії `005a85c1` (до запису settings.json-рішення): API-ключ спочатку переведено з `localStorage` на читання через env-змінну — нова Tauri-команда `omlx_config` у `lib.rs` читала `OMLX_BASE_URL`, `OMLX_MODEL`, `OMLX_API_KEY` зі `std::env` і повертала JSON фронту; `loadEnv()` у `use-omlx.js` викликала `invoke('omlx_config')` і перекривала localStorage-дефолт. Цей підхід усував ручне введення у UI, але не вирішував проблему GUI `.app` запущеного з Finder/Dock (не бачить `~/.zshrc`). Саме це обмеження стало головним аргументом на користь прямого читання з `~/.omlx/settings.json`.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
session: 6220396b-6750-408d-9018-b5153cdb0963
3+
captured: 2026-06-16T06:25:46+03:00
4+
transcript: /Users/vitaliytv/.claude/projects/-Users-vitaliytv-www-nitra-task/6220396b-6750-408d-9018-b5153cdb0963.jsonl
5+
---
6+
7+
The script has been updated — `tauri` now calls the binary directly instead of recursively calling itself via `--cwd=app`.
8+
9+
Running `bun run start` should work now (the `tauri` binary is hoisted to root `node_modules/.bin/` and is accessible from the workspace).
10+
---
11+
12+
## ADR Виправлення рекурсивного виклику `tauri` скрипта в `app/package.json`
13+
14+
## Context and Problem Statement
15+
Запуск `bun run start` завершувався помилкою `ENOENT: Could not change directory to "app"`. Кореневий скрипт `start` у `package.json` делегував виклик в `app/` через `bun --cwd=app run tauri dev`, але скрипт `tauri` у `app/package.json` рекурсивно повторював `bun --cwd=app run tauri`, намагаючись перейти в неіснуючу директорію `app/app/`.
16+
17+
## Considered Options
18+
* Виправити скрипт `tauri` у `app/package.json`, щоб він викликав бінарний файл `tauri` безпосередньо
19+
* Інші варіанти в transcript не обговорювалися.
20+
21+
## Decision Outcome
22+
Chosen option: "Виправити скрипт `tauri` у `app/package.json`, щоб він викликав бінарний файл `tauri` безпосередньо", because бінарний файл `tauri` вже піднятий (hoisted) у кореневий `node_modules/.bin/` через workspaces і доступний з будь-якого workspace без додаткового `--cwd`.
23+
24+
### Consequences
25+
* Good, because `bun run start` більше не впирається в рекурсивну помилку шляху — `app/package.json` тепер викликає `tauri` напряму.
26+
* Bad, because transcript не містить підтверджених негативних наслідків.
27+
28+
## More Information
29+
- Файл змінено: `app/package.json`, скрипт `tauri`: `"bun --cwd=app run tauri"``"tauri"`
30+
- Скрипт `android` у `app/package.json` також містив аналогічну рекурсію (`"bun --cwd=app run tauri android dev"`) — виправлено у тому ж коміті
31+
- Бінарний файл `tauri` зареєстрований у `node_modules/.bin/tauri` кореневого workspace
32+
- Залежність `@tauri-apps/cli` підключена через `app/package.json` як devDependency

0 commit comments

Comments
 (0)