# Changelog

Všechny významné změny tohoto projektu jsou dokumentovány v tomto souboru.

Formát vychází z [Keep a Changelog](https://keepachangelog.com/cs/1.1.0/), verzování dle [Semantic Versioning](https://semver.org/lang/cs/).

## [Unreleased]

## [v0.10.2] — 2026-05-12

### Added — Proxmox LXC deployment prep (single-user cutover)

- **`lib/paths.py`** — centrální env-var resolver pro všechny user-specific cesty. Konstanty `DATA_DIR`, `DOWNLOADS_DIR`, `REPO_DIR`, `JOURNAL_DIR` + convenience `GMAIL_TOKEN_FILE`, `SIGNING_CERT_FILE`, atd. Defaulty zachovávají Mac chování (`~/Documents/AI MD`, `~/Downloads`, `~/cowork-dashboard`). Na LXC se přepíše přes `Environment="COWORK_*_DIR=..."` v systemd unit souboru.
- **`dashboard_server.py`** — `SPISOVY_PROTOKOL_PATH` a runtime `journal_path` přes `_paths.DATA_DIR` / `_paths.JOURNAL_DIR` místo natvrdo `Path.home() / "Documents" / "AI MD"`.
- **`downloads_watcher.py`** — `DOWNLOADS`, `TRIAGE_DIR`, `UPLOADED_DIR`, `JOURNAL`, `_HEARTBEAT_FILE`, `DASHBOARD_ROOT` přes `lib.paths`.
- **`lib/doc_signer.py`** — `DEFAULT_CERT`, `DEFAULT_STORE` z `lib.paths` (fallback na `Path.home()` při samostatném importu mimo dashboard server).
- **`lib/version_tracker.py`** — `DEFAULT_STORE` z `lib.paths`.
- **`deploy/systemd/`** — 4 systemd unit soubory mirroring původních macOS launchd plistů: `cowork-dashboard.service` (KeepAlive, sandbox), `cowork-watcher.service` (KeepAlive, depends on dashboard), `cowork-alerts.service` (oneshot) + `cowork-alerts.timer` (denně 08:00 + 14:00 stejně jako StartCalendarInterval).
- **`deploy/provision-lxc.sh`** — idempotentní bash skript pro čerstvý Debian 12 LXC: apt deps (python, node, libreoffice, exiftool, cups, hplip, samba), system user `cowork`, adresářová struktura v `/var/lib/cowork/`, Python venv, pip install, systemd install+enable, volitelně tiskárna IPP + Tailscale + Samba share.
- **`deploy/PROXMOX_DEPLOYMENT.md`** — end-to-end runbook od provisioningu LXC v Proxmox UI přes data sync, sanity testy, cutover (stop launchd → start systemd) až po rollback. Multi-user RBAC explicitně out of scope tohoto cutoveru.

### Changed — Inbox UI (uživatelské WIP zahrnuto v tomto bundled commit)

- **`_fetch_unread_inbox`** v dashboard_server.py přepnut z `format="metadata"` na `format="full"` — server nyní stahuje MIME parts a sbírá metadata o přílohách pro každé vlákno.
- **Attachment chips** v inbox řádcích (`dashboard.html`) — názvy příloh se zobrazí jako kompaktní `📎 chip-y` pod subject linií.
- **Compact actions grid** v inbox řádcích — Archivovat / Vyřízeno / Claude / Spam / Zítra přeskládáno do `2×3` grid (~130 px) místo 5 vodorovných tlačítek (~310 px). Body emailu má víc místa pro subject + snippet + attachments.
- **Auto-hide done rows** — `.row.done{display:none !important}` skryje vyřízené řádky okamžitě, server doplní novou várku z Gmailu při dalším page renderu.
- **Expanded email view** — klik na body rozšíří `.row` na plnou šířku panelu (`flex 1 1 100%`), max-height calc(100vh - 80px) pro pohodlné čtení.

### Fixed — Modal ghost-click + drag-to-select (filter modal)

- **Filter modal** (ef-modal) v dashboard.html dostal stejný 3-vrstvý fix jako apply-label modal v commit `7703df0`:
  - `e.stopPropagation()` na inject button (kliknutí na „📥→📁 Filtr" se nepřebublá).
  - 10ms delay + opened-at timestamp při otevření (chrání proti browser ghost-clicku, který se dispatchuje v moment otevření).
  - Mousedown tracking na backdropu — modal se zavře jen pokud **mousedown i click target** byly na backdropu. To řeší výběr textu uvnitř modalu, kdy uživatel táhne myš přes okraj.
- **Auto-reload po filter create** — po úspěšném vytvoření filtru se inbox přenačte (1.2 s po toastu), aby zprávy s aplikovaným retroaktivním labelem zmizely z inboxu místo aby visely beze změny.

## [v0.10.1] — 2026-05-12

### Fixed — Daily Gmail filter 403 / scope degradation (root cause)

- **`GMAIL_SCOPES` v `dashboard_server.py` synchronizován s `authorize_gmail.py`.** Předtím tu chyběl `gmail.settings.basic` (3. scope), takže při refresh access_tokenu (po 1h expiry) server přepsal `gmail_token.json` užším scope setem — Gmail API pak vracelo 403 *„Token lacks gmail.settings.basic scope"* při každém pokusu o vytvoření filtru. Uživatel musel **každý den znovu spouštět `authorize_gmail.py`**, což byl symptom; root cause byl právě ten mismatch mezi authorize a server scope listem.
- **Mtime-based cache invalidation pro `_gmail_service`.** Po re-authu už **není potřeba restartovat dashboard server** — `_get_gmail_service()` sleduje mtime `gmail_token.json` a automaticky vyhodí cache i `_drive_service`, jakmile se token na disku změní. Stejný princip se dá využít při ručních editacích tokenu.
- **Scope verification log při init.** Pokud loaded creds chybí jakýkoli z `GMAIL_SCOPES`, server vypíše `logging.warning` se seznamem chybějících scopes a hintem na re-auth. Diagnostika je tedy okamžitá ze serverového logu, ne až z 403 chyby v UI.

## [v0.10.0] — 2026-05-07

### Added — BRIEF_dokumentacni_pipeline_v2 (sekce V + VI + III.3 fix)

- **Procesní/právní šablony** v `templates/`:
  - `anonymizace-zakaznika.md` (V.1) — pravidla a vzorové formulace pro anonymizaci zákazníka v podáních orgánům a protistraně; GDPR čl. 5 + 6 minimalizace.
  - `preempce-namitky-1828-1829-1820.md` (V.2 + V.3) — § 1828 OZ smlouva mimo obchodní prostory, § 1829 OZ 14denní lhůta, § 1820 OZ prodloužení o 12 měsíců = 12,5 měsíční lhůta jako ekonomická páka.
  - `preempce-namitky-druha-navsteva-na-vyzvu.md` — rebuttal námitky „druhá návštěva byla na výzvu spotřebitele" se odkazy na NS 33 Cdo 1356/2017, SDEU C-485/17.
  - `preempce-namitky-zvukovy-zaznam-bez-souhlasu.md` (V.4) — § 88 odst. 1 OZ zákonná licence, judikatura NS 30 Cdo 64/2012, 7 Tdo 84/2010, ÚS II. ÚS 1774/14, ESLP Schenk v. Switzerland 1988, GDPR čl. 6/1/f.
  - `recapture-strategie.md` (V.5) — INTERNÍ T+3M / T+9M plán „převzetí, které se neodmítá"; využití § 1820 + § 1829 odst. 2 OZ jako ekonomické páky proti konkurenci po podomním prodeji.
  - `interni-dokument-hlavicka.md` (V.6) — hlavičkový block „⚠ INTERNÍ DOKUMENT — nepředávat protistraně, neodesílat orgánům"; vazba na `sendableBasename: null` ve wrapperu.
  - `spisova-sablona-master.md` (VI) — kanonický vzor adresářové struktury spisu, pojmenovacích konvencí, defenzivní pravidla a workflow založení nového spisu; tie-in všech V.* šablon.
- **III.3 — `antiExtract: true|false` per-dokument toggle** v `generate_polished_podomni-prodej-iakimov.js`. Každý doc má explicitní field. Logic: `(doc.antiExtract ?? Boolean(MAKE_PDF)) && MAKE_PDF` — master gate `MAKE_PDF=1` zůstává jako ochrana proti náhodnému běhu pipeline během dev. Per brief: MP, ČOI, T-Mobile, SVJ, Trigema, protokol → `false` (orgán/compliance potřebuje fulltext indexaci).

### Fixed — Watcher `_to_send/` exclusion (BRIEF v2 § VII.5)

- **`_auto_add_phase_from_upload`** v `dashboard_server.py` — guard rozpozná „non-watcher filename" (žádný `YYYY-MM-DD__` date prefix ani `priloha_\d+_\d+_…` raw ČTÚ formát) a fázi nevyrobí. Defenzivní vrstva: i kdyby sendable PDF z `_to_send/` zatoulalo do watcher pipeline, nepollutuje chronologii spisu fallback fází.
- Regression test `tests/test_auto_phase_chronology.py::test_sendable_basename_has_no_actor_match` ověřuje, že 7 reálných sendable basenames (Vyzva_pro_T-Mobile_…, Oznameni_pro_Mestskou_policii_…, Podnet_pro_Ceskou_obchodni_inspekci, …) nematchuje žádný `_ACTOR_PATTERNS`.

## [v0.9.9] — 2026-05-07

### Fixed

- **Locale-stable subprocess parsing** — `get_default_printer()` a `list_printers()` v `lib/label_action_runner.py` selhávaly na macOS s českou systémovou lokalizací (`Region: Czechia`), kde `lpstat` vrací české řetězce (*„výchozí systémový cíl:"* místo *„system default destination:"*). Dashboardový tab „🖨 Label akce" hlásil *„Žádná tiskárna nenalezena"*, přestože tiskárna nakonfigurovaná byla.

### Added

- **`lib/subprocess_helpers.py`** — nový sdílený helper `run_posix(args, ...)` a `_posix_env()` vynucující `LANG=C LC_ALL=C` pro POSIX-stable výstup CLI nástrojů nezávisle na systémové lokalizaci.
- Všechna `subprocess.run` / `check_output` volání v `lib/label_action_runner.py`, `lib/pdf_anti_extract.py`, `extract_officials_summary.py`, `forensic_scan_pdf.py` a `dashboard_server.py` (launchctl, git), která parsují stdout/stderr, přesunuta na `run_posix()`.
- Regresní testy `test_get_default_printer_czech_locale` a `test_list_printers_czech_locale` ověřující, že `LC_ALL=C` je vždy nastaveno.

## [v0.9.6] — 2026-05-06

### Fixed

- **Source file label stripping** — regex `[^_]+` nahrazen lazy `.+?` pro správné oříznutí prefixu `YYYY-MM-DD__CTU-NNNN_YYYY-NNN__` ze jmen zdrojových souborů fází. Dřívější regex se zasekl na `_` uvnitř CTU ref (`CTU-3513_2026-624`) a zobrazoval celé jméno místo jen slugu (např. `vyzva-k-vyjadreni-pred-rozhodnutim.pdf`).
- **Firma strip (sidebar)** — potvrzena funkčnost sticky proužku; height 0 při `display:none` panelu je správné chování, po přepnutí na záložku Řízení strip správně zobrazuje 11 tlačítek.

## [v0.9.5] — 2026-05-06

### Added

- **`source_file` pole ve fázích** — každá fáze vytvořená backfillem nebo auto-add z uploadu nese název zdrojového dokumentu (`source_file`). Backfill ukládá filename z `document_versions.json`, auto-add ukládá nahraný soubor. POST `/phase` endpoint ho také přijímá pro manuálně přidané fáze.
- **`warn_urgency` na případu** — `_compute_case_derived()` nově počítá `warn_urgency` jako `"critical" | "urgent" | "warning" | null`. Klíčový detail: filtruje fáze s `date_received` starším než 180 dní — staré backfillované fáze z 2021–2023 nepollutují aktuální varování. Pouze fáze bez `date_received` (manuálně přidané) nebo s recentním datem se počítají.
- **Source display ve fázích** — pokud fáze má `source_file`, zobrazí se pod datem doručení jako klikatelný odkaz `📄 <název>` vedoucí přímo na Drive dokument (cross-reference přes `case.spis[]`). Pokud Drive ID nenajde, zobrazí se plain text.
- **Warn badge na kartě** — případy s `warn_urgency` mají v pravém horním rohu karty badge: `⚠ PO LHŮTĚ` (červený, pulzující) / `⏰ URGENTNÍ` (oranžový) / `📅 TERMÍN` (modrý). Badge se nezobrazí pro historické neuzavřené případy bez aktuálních termínů.

## [v0.9.3] — 2026-05-06

### Added

- **`date_received` pole ve fázích** — každá fáze vytvořená backfillem nebo auto-add z uploadu nese datum doručení (`date_received`) odvozené z `uploaded_at` v `document_versions.json`. Datum je tedy reálný historický čas nahrání souboru do Drive, ne dnešní datum. Manuálně přidávané fáze mají v dialogu nové pole „doručeno (datum příjmu)".
- **Zobrazení zbývajících dní v renderu fází** — každá fáze s `due_date` zobrazuje `⏰ do D. M. YYYY — zbývá N dní` (zeleně > 7 dní, oranžově ≤ 7 dní, červeně po termínu). Datum doručení se zobrazuje jako `📅 doručeno D. M. YYYY` vedle termínu.

### Fixed

- **Rozšíření `_PHASE_SLUG_MAP`** — regex pro „Vyjádření k podkladům před rozhodnutím" upraven z `podklad` na `podkl`, aby pokryl zkrácené názvy příloh z ČTÚ (`Vyjadreni_k_podkl._pred_vydanim…`). Regex pro „Zahájení řízení" sloučen a rozšířen (odstraněna duplikátní položka). Backfill všech případů po opravě přidal celkem 39 nových fází do 17 spisů.

## [v0.9.2] — 2026-05-06

### Added

- **Auto-přidání fáze z uploadovaného dokumentu** — po každém úspěšném uploadu server parsuje slug z názvu souboru (`YYYY-MM-DD__CTU-NNNN__<slug>__firma.ext`) a mapuje ho na název fáze: `vyzva-k-vyjadreni` → „Výzva k vyjádření (+15d)", `nase-rozklad` → „Rozklad podán", `rozhodnutí` → „Rozhodnutí doručeno (+30d)", `vyjádrení-k-podkladum` → „Vyjádření k podkladům před rozhodnutím" atd. Regex pokrývá varianty s diakritikou i bez. DS potvrzení (`dorucenka`, `potvrzeni-odeslani`) fázi nepřidají — řeší je stávající auto-complete. Dedup: pokud případ fázi se stejným názvem již má, nepřidává se duplikát. Nová fáze dostane `current_task = "📥 Dokument přijat: <filename>"` a volitelný `due_date`.
- **Toast `phase_added`** — dashboard notification polling zobrazí toast `📥 Nová fáze: …` a provede `rzReload()` pro okamžité zobrazení v kartě.

## [v0.9.1] — 2026-05-05

### Fixed

- **Roleta (accordion) — collapse pouze přes ▲ šipku** — `rzToggleExpand` nyní rozlišuje stav: při sbalení kliknutí expanduje celý `.rz-head`; při rozbalení kliknutí sbalí pouze `.rz-chev` (▲ šipka). Kliknutí na název fáze, poznámku nebo dokument uvnitř rozbalené karty accordion nesbalí.
- **Cursor cleanup** — `.rz-card` má `cursor:default`; `.rz-head` (sbalená karta) `cursor:pointer`; `.rz-card.expanded .rz-head` zpět `cursor:default`; `.rz-chev` vždy `cursor:pointer` s hover efektem.
- **Spisový manifest — collapsible** — tabulka souborů je ve výchozím stavu skryta; zobrazí se tlačítkem `▼ N souborů`. Brání, aby velký spis (22+ souborů) vizuálně přehluší přehled fází. Stav otevření je udržován v `_rzSpisOpen` bez plného re-renderu.
- **Stale localStorage firma key** — `rzResolveDefaultFirma()` nyní validuje uloženou hodnotu vůči aktuálním datům. Zastaralá zkratka (např. "TR" po přejmenování na "13.net") je ignorována a fallback se spočítá z urgency.

## [v0.9.0] — 2026-05-05

### Added

- **Spisový manifest** v záložce Řízení — dynamický JOIN `document_versions.json` ↔ `rizeni.json` přes `folder_id`; každý případ zobrazuje tabulku nahraných souborů s verzí, datem a 🔐 badge pro PAdES-B-B podepsané dokumenty.
- **Fast-path klasifikace** — dokumenty s CTU ref v názvu souboru (format `CTU-NNNN_YYYY-NNN`) jsou klasifikovány lokálním regexem bez LLM volání; cache `rizeni.json` s 60s TTL; ~80 % reálných souborů obslouzeno okamžitě.
- **Retry queue** ve watcheru — failnuté uploady se zapíší do `_retry_queue.jsonl` místo přesunutí do triage; při restartu watcheru se fronta prochází a pokouší znovu (max 3 pokusy).
- **DS auto-complete fází** — po úspěšném uploadu souboru jehož název matchuje DS potvrzení (`potvrzeni-odeslani`, `dorucenka`, …) se automaticky označí poslední aktivní fáze případu jako dokončená.
- **QR kód v zápatí DOCX** — každý vygenerovaný dopis obsahuje v zápatí QR kód kódující č.j., firmu a datum; layoutu vyřešen přes `TabStopType.RIGHT` (předchozí Table-based přístup způsoboval rotaci textu v LibreOffice).
- **`GET /notifications?since=<iso>`** — endpoint čte `dashboard_inbox.jsonl`, vrací položky novější než `since`; dashboard polluje každých 15s a zobrazuje toast při `type=phase_autocomplete`.
- **`_inbox_notify()`** helper — unifikovaný zápis do `dashboard_inbox.jsonl` pro všechny server-side eventy.

### Fixed

- **`rzToggleExpand` — kliknutí na odkaz nezavírá accordion** — guard změněn z `tagName === "BUTTON"` na `closest("a, button")`; pokrývá všechny kotvy i tlačítka uvnitř karet (Drive link, dokumenty ve fázích, spisový manifest).
- **`_drive_folder_id()`** — extrakce `folder_id` z URL `/drive/folders/ID` (předchozí regex `/d/ID` fungoval jen pro file URLs).
- **`import logging`** — chybějící import způsoboval `NameError` při DS auto-complete a prázdnou odpověď `/save_to_drive`.
- **Route regex** pro POST/DELETE `/rizeni/<id>/phase/<pid>` rozšířen z `[0-9a-fA-F\-]+` na `[\w\-]+`; pokrývá slug IDs jako `op-3585-rozklad`.

---

## [v0.8.2] — 2026-05-05

### Fixed

- **Watcher: healthcheck false-positive** — `archive_uploaded()` nyní přijímá `drive_name` a archivuje soubor pod kanonickým názvem z Drive (= `suggested_filename` z LLM klasifikátoru). Předchozí chování ukládalo originální název souboru do `_uploaded/`, zatímco `document_versions.json` eviduje Drive název → healthcheck porovnával různé řetězce → legitimně uploadnuté soubory byly označeny jako orphans a přesunuty do `_orphaned/`. Recovery: 4 soubory z `_orphaned/2026-05-05/` přesunuty zpět do `_uploaded/` pod kanonickými názvy.

---

## [v0.8.1] — 2026-05-05

### Fixed

- **Watcher: fsevents dedup (debouncing)** — `DebouncingHandler` agreguje eventy per-path s 500ms oknem; watchdog/FSEvents posílal CREATE+MODIFY+CLOSE trojici → každý soubor se teď zpracuje jednou místo 2–3×.
- **Watcher: race condition mezi workery** — per-path mutex (`threading.Lock`, `blocking=False`); druhý worker při překryvu tiše vrátí místo zahájení paralelního pipeline pro stejný soubor.
- **Watcher: atomic upload order** — pořadí operací opraveno na: log → journal → archive; archive move je vždy POSLEDNÍ krok, takže crash před ním nechá soubor v `~/Downloads/` (recoverable), nikoli v `_uploaded/` bez Drive záznamu.
- **Watcher: startup orphan healthcheck** — při startu watcher projde `_uploaded/<datum>/` a každý soubor, který nemá záznam v `document_versions.json`, přesune do `_orphaned/<datum>/` pro ruční zpracování; log `healthcheck: N orphan(s)`.

---

## [v0.8.0] — 2026-05-05

Implementace všech čekajících briefů z 30. 4. 2026: oprava silent-reset bugu, live Watcher tab, version footer, case podomní prodej.

### Added

- **`📡 Watcher` tab** v dashboardu — live tail `watcher.log` s color-codingem (zelená=uploaded, žlutá=triage, červená=error), polling každé 2s, auto-scroll, stats counter (uploaded/triage/errors dnes), indikátor zda watcher daemon běží, tlačítka Pause/Resume/Clear.
- **`/watcher/log/tail` endpoint** v `dashboard_server.py` — efektivní seek-based tail, today stats, `launchctl` check running status.
- **Version footer** vpravo dole na všech tabech — chip `v0.8.0 (commit)`, hover/click popover s posledními 3 verzemi z CHANGELOG.md, dirty indikátor ●.
- **`/version` endpoint** — git tag + short hash + dirty + parsovaný CHANGELOG (3 sekce).
- **`/CHANGELOG.md` endpoint** — serve raw souboru pro „Celý CHANGELOG →" link.
- **`tr-2026-podomni-prodej-jeremiasova` case** přidán do `rizeni.json` — 8 fází, deadline 2026-05-14 (info klientům o odstoupení), TR/2026/podomni-prodej.
- **`templates/podomni-prodej/`** v `~/Documents/AI MD/templates/` — 6 reusable šablon (oznámení MP, podnět ŽÚ, podnět ČOI, dopis SVJ, info klientovi, PLAN) pro spisy proti podomnímu prodeji konkurence v domech, kde firma ze skupiny KPE poskytuje služby.
- **`templates/playbook-zalozeni-spisu.md`** — interaktivní playbook („minipivovar") pro Cowork session, spouští se trigger frází „založ případ" / „založ spis". 10-step decision tree přes AskUserQuestion: typ věci → firma podavatele (z decision matrix) → detaily incidentu → č.j. → adresářová struktura → naplnění šablon → DOCX → PLAN.md → update rizeni.json → drop do Downloads.
- **Decision matrix § 10** v `spisovy_protokol.md` — kdo z firem skupiny je odesílatelem v jakém typu věci. Klíčové pravidlo: podavatelem je firma, jejíž obchodní zájem byl jednáním protistrany dotčen.
- **§ 11 — Workflow založení nového spisu** v `spisovy_protokol.md` s odkazy na playbook + per-typ šablony.
- **Klasifikační pravidla (j) (k) (l)** v `spisovy_protokol.md` § 3.
- **Quick reference** v `spisovy_protokol.md` § 9 rozšířena o 5 nových destinací (MP, ČOI, ŽÚ, SVJ, klient).
- **`_restore_rizeni_now.sh`** — rychlý recovery script.
- **První pilotní spis přes nový systém:** Trinactka 2026-04-30 podomní prodej Jeremiášova 2722/2b, 9 dokumentů, 3 personalizované info dopisy klientům (Černý / Piroha / Kropuch).

### Changed

- **`rizeni.json` schema** rozšířeno o pravidla pro `category: "spravni_rizeni"` v non-ČTÚ kontextu.

### Fixed

- **Silent-reset antipattern** v `dashboard_server.py` — `_rizeni_atomic` a `_rizeni_load_locked` při `JSONDecodeError` nově **vyhazují výjimku** (fail-loud) místo tichého resetu na `{"cases": []}`. Odstraňuje riziko ztráty celého `rizeni.json` při race condition nebo schema mismatch.

---

## [v0.7.0] — předchozí release

(Historické verze nebyly dokumentovány v CHANGELOG; viz git tag log.)

## [v0.6.1] — předchozí release
## [v0.6.0] — předchozí release
## [v0.5.0-rc2] — předchozí release
## [v0.5.0-rc1] — předchozí release
## [v0.4.0] — předchozí release

---

[Unreleased]: https://github.com/michalpeterka-wq/cowork-dashboard/compare/v0.8.0...HEAD
[v0.8.0]: https://github.com/michalpeterka-wq/cowork-dashboard/compare/v0.7.0...v0.8.0
[v0.7.0]: https://github.com/michalpeterka-wq/cowork-dashboard/releases/tag/v0.7.0
