# Changelog
All notable changes to this project are documented in this file. Format loosely
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [1.5.0] - 2026-08-05
### Added
- **Admin log viewer** (Admin > Logi) — browse `storage/logs/*.log` from the
admin panel without shell access to the container: a file picker (size +
last-modified, most recent first), level/free-text/entry-count filters, and
an optional 5s auto-refresh. Read-only, admin-only; reads only the tail of
large files to keep it fast.
- **"Bez kategorii" filter** in the operator queue's category dropdown —
isolates tickets with neither a category nor subcategory assigned (e.g. an
IMAP mailbox routed to nothing in particular), previously only reachable by
scanning the unfiltered list.
### Changed
- **Hesk import tool** (`scripts/hesk-import/`) no longer auto-creates client
accounts for unrecognized requester e-mails — the servicedesk user base is
now treated as authoritative, so a Hesk ticket whose requester has no
matching account is skipped instead (reported at the end, with the list of
skipped e-mails). Hesk staff replies/notes and ticket ownership are now
linked to real operator/admin accounts when the staff e-mail matches one.
Two new backfill flags cover tickets imported before these existed:
`--assign-operators` (sets `assignee_id` from Hesk's ticket owner, never
overwriting a manual reassignment) and `--fix-closed-dates` (corrects a
closed ticket's date to Hesk's own `closedat` column instead of the
drifting `lastchange`, and adds the missing "Zamknięte" history entry).
Every newly imported ticket also records its source Hesk id
(`tickets.hesk_ticket_id`, unique) as a second, DB-level guard against
duplicate imports on top of the existing state file. See
`scripts/hesk-import/README.md` for details.
- Internal database cleanup: removed three columns confirmed unused against
live data (`users.remember_token`, `users.email_verified_at`,
`email_templates.trigger_label`); moved custom field values, AI
triage/summary state, and the linked Snipe-IT asset off the `tickets` row
into three dedicated one-to-one tables (`ticket_field_values`,
`ticket_ai_summaries`, `ticket_snipeit_assets`) — no visible behavior
change, but custom field values are now efficiently queryable instead of
living only in a JSON blob, and the `tickets` row itself is narrower; added
missing reverse indexes on 4 pivot tables (`team_subcategory`, `role_user`,
`team_user`, `custom_field_subcategory`); `tickets.source` and
`ticket_messages.source` now validate against a known set of values
instead of silently accepting any string.
### Fixed
- The operator statistics dashboard's category/subcategory breakdown (and
its category filter) only counted tickets routed through a subcategory —
a ticket routed to a whole category with no subcategory (e.g. via an IMAP
mailbox routed to "całą kategorię") was silently excluded from those
charts and from filtering by that category. Now counted correctly, with
bare-category tickets shown as their own "(bez podkategorii)" row in the
subcategory breakdown.
- A JavaScript error (and stray background timers left running) could occur
when navigating away from a page with an active countdown/timer widget —
ticket/queue auto-refresh, the theme switcher, file-attachment
drag-and-drop. Most consequential in the operator ticket time tracker,
where it could throw console errors and momentarily break other UI
elements (e.g. dropdown menus) after leaving a ticket with the timer
running.
## [1.4.0] - 2026-08-05
### Added
- **Active Directory support for LDAP auth** (Admin > Integracje > "LDAP /
Active Directory") — a "Typ katalogu" dropdown switches between LLDAP/
OpenLDAP (the original, still the default) and Active Directory. AD uses a
different schema (no inetOrgPerson/posixAccount, a binary `objectGUID`
instead of `entryUUID`) and login attribute (`sAMAccountName`, not `uid`);
both are now auto-detected from the directory type instead of requiring
LLDAP's schema everywhere.
- **Operator client search** (Operator > Klienci) — a dedicated search page
(name or e-mail, any account, not just role=client) showing each match's
role badges and ticket count, linking straight into the queue pre-filtered
to that customer.
- **Paginated ticket lists** — the operator queue (50/page) and client
dashboard (20/page, current/archive tracked as separate pages so switching
tabs doesn't lose your place) no longer render every matching ticket at
once; sorting still happens over the full filtered result first.
- **Command-palette global search (Ctrl+K / Cmd+K)** — searches tickets from
anywhere in the app, scoped to what the searching user can actually see
(operators/admins search everything visible to them, clients only their
own). Supports Gmail-style operators — `od:` (reporter), `temat:`
(subject only), `treść:`/`tresc:` (message content only), `numer:`/`nr:`
(ticket number), combinable and AND'd together (`od:kacper
temat:drukarka`) — plain text with no operator still searches everything
as before. A "Szukaj" button in the operator/admin sidebars opens the same
dialog for anyone who doesn't know the shortcut.
- **Recently viewed tickets** (operator sidebar) — the last 6 tickets an
operator actually opened, most-recent first, re-bumped (not duplicated) on
a repeat visit.
- **Navbar redesign: panel switcher.** The old dropdown-only role switcher
(in the profile menu) and the plain "Panel Klienta/Operatora/Administratora"
text label are replaced by a single segmented control centered in the top
bar — Klient / Operator / Administrator — showing only the areas the
logged-in user actually holds, highlighting the current one, and sliding a
preview to whichever option is hovered before you click. Adapts to a
full-width row below the icons on narrow screens instead of overlapping
them.
- **Richer profile dropdown** — now shows the account's name, e-mail, and
role badges above the existing Powiadomienia/Wyloguj się links.
- **Per-page browser tab titles** — every page sets its own `
`
(e.g. the ticket subject, the selected queue, the active admin tab)
instead of every tab just showing the company name, always anchored with
the company name as a suffix so it's still identifiable once the browser
truncates a long tab title.
- **Hesk 3.x historical import** (`scripts/hesk-import/`) — a one-time,
read-only migration of tickets (with full reply/note history) from an old
Hesk helpdesk database, filtered by e-mail domain. Dry-run by default,
resumable, auto-maps categories (exact name match) and teams (when
unambiguous). See `scripts/hesk-import/README.md`.
### Fixed
- Pagination controls ("« Poprzednia" / "Następna »") were showing the raw
translation keys `pagination.previous`/`pagination.next` instead of
actual text — the app's `APP_LOCALE=pl` had no matching `lang/pl/`
translation file and no English fallback (`APP_FALLBACK_LOCALE` is also
`pl`), so Laravel had nothing to resolve those strings to.
## [1.3.0] - 2026-07-27
### Added
- **Snipe-IT asset inventory integration** (Admin > Integracje), optional and
off by default — connects to a Snipe-IT instance by API address + personal
API token (plus a "Nie sprawdzaj SSL" toggle for self-signed instances) and
surfaces three independently switchable capabilities:
- **Klient może wybrać sprzęt, którego dotyczy zgłoszenie** — while
creating a ticket, a client sees the devices checked out to them in
Snipe-IT (matched by e-mail) and can pick the one the ticket is about.
Scoped to admin-selected subcategories via a multi-select picker that
only appears once this is turned on — same "nothing shows until
explicitly opted in" convention as BookStack's shelf allow-lists.
- **Operator może zobaczyć sprzęt zgłaszającego w widoku zgłoszenia** — the
same per-requester asset list, shown in a sidebar card on the ticket
view, with a "Powiąż" button per item.
- **Zezwól operatorowi na przeszukiwanie całego inwentarza** — a search
box + button in the same sidebar (not a separate page) letting an
operator link any asset in Snipe-IT, not just the requester's own — for
shared equipment like printers.
- A linked asset shows live status/category/current assignment (fetched
fresh from Snipe-IT, not just the cached label) with an "Odepnij" button
that stays available to the operator regardless of the two toggles above
— clearing an existing link is a correction, not new Snipe-IT access.
Every asset is displayed as "numer środka - numer seryjny - producent
model" plus its Snipe-IT category, joining whichever of those pieces are
actually present.
- **AI summary: manual regenerate + regenerate on new message.** A
"Wygeneruj teraz" button now sits on the operator's "Podsumowanie AI" card
for an immediate, on-demand refresh. Separately, a new admin toggle
("Regeneruj podsumowanie od razu po każdej nowej wiadomości", off by
default) re-runs the summary right after any reply/note lands on a ticket,
instead of only ever picking it up on the next scheduled
`ai:run-ticket-automation` sweep. The transcript sent to the model now also
includes the ticket's own opening body text (previously only the reply
thread), fixing summaries silently missing the original request on long
tickets whose first message had scrolled out of the transcript window.
### Fixed
- The status dropdown in the operator ticket view could keep showing the
pre-change status after sending a reply via a status-changing quick action
(e.g. "Wyślij i oznacz jako rozwiązane") until the next full page load — a
Livewire/Alpine-morph quirk for `