Files
servicedesk/CLAUDE.md
Kacper 0943829331 v1.5.1
Co nowego:
- Kolumny kolejki operatora: dwie nowe (ID, e-mail) obok istniejących, oraz
  możliwość zmiany kolejności widocznych kolumn strzałkami ↑/↓ w picker
  „Kolumny” — nie tylko włączanie/wyłączanie. Kolejność zapamiętywana jest
  per operator tak samo jak dotąd widoczność.
- Wybór sprzętu klienta (Snipe-IT): druga lista dozwolonych kategorii obok
  istniejącej listy podkategorii — pozwala objąć od razu wszystkie
  podkategorie danej kategorii jednym zaznaczeniem.
- Pulpit klienta i widok zgłoszenia operatora pamiętają teraz aktywną
  zakładkę, więc „Wróć do listy” wraca do tej samej, a nie zawsze do
  domyślnej.

Poprawki:
- Paginacja (kolejka operatora, pulpit klienta) używała domyślnego,
  szarego stylu Laravela reagującego na motyw systemu/przeglądarki, a nie
  przełącznik jasny/ciemny w aplikacji — stąd ciemne przyciski nawet w
  trybie jasnym. Podmieniony na własny widok zgodny z kolorami aplikacji
  (w tym własne tło/border każdego przycisku i wyśrodkowanie na telefonie).
- Kolorystyka boksu z informacją o logowaniu nie zmienia już odcienia
  między trybem jasnym i ciemnym (wcześniej pochodziła z --color-accent) —
  teraz stałe, ciemne tło w obu trybach, więc kolory tekstu ustawione przez
  admina (np. biały) zostają czytelne niezależnie od motywu. Poszerzona
  karta logowania (380px → 480px).
- Lista zgłoszeń klienta: etykiety priorytetu/statusu nie zawijają się już
  do osobnej linii przy długim temacie na wąskich ekranach — zostają
  przypięte do prawej, a temat zawija się we własnej kolumnie.
- Liczniki czasu pracy (resumeTimer/stopTimer/...) nie dotykają już
  updated_at — samo otwarcie zgłoszenia nie liczy się jako aktualizacja.
  Kolejka operatora i pulpit klienta domyślnie sortują po dacie utworzenia
  z tego samego powodu.

Zaktualizowana dokumentacja: README, CLAUDE.md, ARCHITECTURE.md,
CHANGELOG.md, wiki/admin, wiki/client, wiki/operator.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 09:01:34 +02:00

9.4 KiB

CLAUDE.md

Guidance for AI coding agents working in this repo. See README.md for the feature/tech overview, ARCHITECTURE.md for how the app is put together, and install.md for full deployment instructions.

This is a live production system

The only real user today is the owner's own account (client+operator+admin roles). Treat the running database as production, not a sandbox:

  • Never create/modify/delete real DB records (users, tickets, settings, etc.) without the user explicitly asking. Don't spin up throwaway test users via php artisan tinker against this DB.
  • For UI/CSS verification, prefer an offline static-HTML harness reproducing the real markup+CSS, or ask for test credentials, rather than poking at prod data.
  • Production is fronted by Traefik with a private-CA TLS cert (not publicly trusted) — tools like curl/Playwright need -k / ignore_https_errors to hit it directly.

Container operations: use sudo, never build the image locally

All Docker commands against this stack need sudo (e.g. sudo docker compose exec app ..., sudo docker exec servicedesk-app-1 ...).

The stack is four services sharing the one servicedesk image — app (Apache, what actually serves HTTP), reverb (websocket server, php artisan reverb:start), cron (scheduler loop, php artisan schedule:work — see below), and mariadb. Only app and reverb are reachable from Traefik.

Never run docker build, docker compose build, or --build. The servicedesk image is built by CI (.gitea/workflows/build.yml, triggered on Dockerfile changes on main) and pushed to the Gitea container registry; compose.yaml only ever pulls a tag (sudo docker compose pull && sudo docker compose up -d, see install.md 1.3/1.3a) — building locally would just diverge from what CI produces. The app container (servicedesk-app-1) mounts ./src from the host over NFS (/mnt/rabbit-containers → NFS export), so plain file edits already take effect with no rebuild or restart:

  • Blade/PHP/route edits: just edit the files — live instantly, no restart needed.
  • resources/css/app.css / resources/js/* (Vite/Tailwind entrypoints) do need a rebuild — public/build/assets/ is static compiled output. Neither the host nor the app container has Node installed; rebuild with a throwaway container instead of touching the app image:
    sudo docker run --rm -v "$(pwd)/src":/app -w /app node:22 npm run build
    
    (run from the repo root; node_modules already exists, no install needed). Verify via public/build/manifest.json picking up a new hash.
  • After running artisan cache commands for diagnostics (config:cache, view:cache), clear them again afterward (config:clear/view:clear) — this app normally runs uncached so edits apply live; leaving a cache on silently breaks that workflow.
  • sudo docker exec runs as root, not www-data. Apache's worker processes run as www-data; anything you run via a plain docker exec (php artisan test, tinker, view:cache, etc.) runs as root. On this NFS-backed mount, a file Blade compiles/caches while running as root (storage/framework/views/*.php) can't later be overwritten by www-data when a real request needs to recompile it (the source changed) — this surfaces in production as a 500 with touch(): Utime failed: Operation not permitted. If you ran php artisan test/tinker/any artisan command via docker exec in a session where you also edited Blade files afterward, finish with sudo docker exec servicedesk-app-1 php artisan view:clear to flush any root-owned compiled views before ending the session — don't wait for a report of a broken page to catch it.

Scheduled commands run in the dedicated cron container

The Docker image ships no cron/supervisor of its own (see install.md), so tickets:check-sla-breaches, automation:run-rules, emails:fetch-imap, and ai:run-ticket-automation (all registered in routes/console.php via Schedule::command(...)) only ever run if something calls php artisan schedule:run on a timer. As of 2026-08-04 this is the cron service in compose.yaml — same servicedesk image, running php artisan schedule:work (Laravel's own foreground scheduler loop, ticks every minute internally, no external trigger needed). Before this it was a root crontab entry on the host calling docker compose exec -T servicedesk schedule:run; that entry has been removed from sudo crontab -l -u root now that the container replaces it — don't re-add it, the two would double-run every scheduled command.

If the cron container isn't running (sudo docker compose ps cron), none of the four scheduled commands fire — same failure mode as the old missing-crontab case, just check the container instead of the crontab. IMAP-specific activity (connect attempts, per-message accept/reject decisions, created/replied ticket ids) is logged separately from the app's normal LOG_LEVEL to storage/logs/imap-*.log (see the imap channel in config/logging.php) — check there first when a mailbox isn't behaving as expected, before assuming the scheduler itself isn't firing. ai:run-ticket-automation and hesk:import get the same always-debug treatment via the ai/hesk_import channels (storage/logs/ai.log/hesk-import.log). All of storage/logs/*.log is also browsable from Admin > Logi (app/Livewire/Admin/Logs.php) if you'd rather not docker exec in just to tail a file.

All four commands' intervals are admin-configurable (Admin > Konfiguracja — schedule_sla_check_minutes/schedule_automation_rules_minutes/ schedule_imap_fetch_minutes/schedule_ai_automation_minutes), which is why routes/console.php registers them as ->everyMinute()->when(fn () => Settings::dueEveryMinutes(...)) instead of a plain ->everyFifteenMinutes()/ ->cron(...) call — never build a cron expression (or otherwise read Settings) at that file's top level. routes/console.php is require'd on every artisan boot (migrate, tinker, php artisan test, not just schedule:run — it's wired in via bootstrap/app.php's commands: key), so a top-level Settings::get(...) call runs before a fresh/test database necessarily has the settings table, and crashes every single artisan invocation, not just the scheduler. A ->when($closure) guard is the fix — the closure is only ever evaluated later, when schedule:run processes due events. One visible side effect: php artisan schedule:list shows * * * * * for all four regardless of the actual configured interval, since that only exists inside the closure — expected, not a bug worth chasing.

Apache /icons/ alias trap

The stock php:apache image enables mods-enabled/alias.conf, which defines Alias /icons/ "/usr/share/apache2/icons/". Any app-level static directory at public/icons/ is silently shadowed by Apache's own stock icon set — requests 404 before ever reaching Laravel, with no useful log line. This project deliberately uses public/pwa-icons/ for PWA manifest icons for exactly this reason — never add a public/icons/ directory.

Established UI conventions

  • Post-login redirect always lands on /client (User::defaultArea() in app/Models/User.php), even for accounts holding operator/admin roles too — staff switch areas via the role switcher in the header (resources/views/components/profile-menu.blade.php). Keep /client first-priority in defaultArea() if you add new roles/areas.
  • Mobile tables: don't rely on horizontal scroll alone for wide data tables. The established pattern (.table-cards-mobile in resources/css/app.css, @media (max-width:640px)) turns each <tr> into a card with data-label attributes on <td>s. Currently applied to the operator ticket queue; apply the same treatment to any other wide table you add or make mobile-relevant (admin panel tables don't have it yet).
  • Ticket list default sort is created_at (or the raw id, which is equivalent — both are monotonic) descending, everywhere a ticket list is shown: the operator queue (App\Livewire\Operator\Queue::$sortBy, default 'created') and both tabs of the client dashboard (App\Livewire\Client\Dashboard::baseQuery(), orderByDesc('created_at')). Deliberately not updated_at — see the timer/updated_at note in ARCHITECTURE.md; even with that fixed, sorting by "last touched" is a worse default for a support queue than a stable creation order. Keep any new ticket list consistent with this rather than defaulting to updated_at.
  • Per-user table customization (shown/hidden columns + their order): the operator queue's pattern (Queue::$visibleColumns, persisted to users.operator_queue_columns via persistVisibleColumns(), reordered with moveColumnUp()/moveColumnDown()) is the template to reuse if another table gains the same feature — auto-save on every toggle/reorder, no explicit "save" step required from the user. This is intentionally separate from SavedQueueView (named, manually-saved, multi-field filter presets); don't conflate the two.

Testing & code style

See TESTING.md and CONTRIBUTING.md — run php artisan test and ./vendor/bin/pint before considering a change done. There is no CI; local test runs are the only gate.