- Generic AI integration (Admin > Integracje > "Integracja AI"), optional and off by default: an OpenAI-compatible /chat/completions client (Groq, OpenAI, or a self-hosted Ollama instance) configured by base URL, optional API key, model, and an SSL-verification toggle. Foundation for the two AI features below and anything else that wants an LLM call in the future. - BookStack automatic content tagging (AI): "Otaguj nową treść"/"Otaguj wszystko ponownie" buttons plus `php artisan bookstack:tag-content` (--dry-run/--force/--limit=N) tag every book/chapter/page with matching helpdesk subcategory names, idempotent by default. - BookStack search refinement: "Przeszukuj" is now three independent checkboxes (Książki/Strony/Rozdziały) instead of a single dropdown, plus a new "Szukaj po" setting (nazwa/tagi/oba) — tag matching uses the bare subcategory name, matching what auto-tagging writes. - AI-driven ticket triage + summary (Admin > Integracje > "Automatyzacja AI dla zgłoszeń", via new scheduled ai:run-ticket-automation): five toggles auto-assign/correct category+subcategory, rewrite an unclear subject, and set priority from content, once per ticket in the background; every change is logged in the ticket's history. Separately, an AI summary + suggested action for every ticket, shown to operators only, with an admin-editable prompt. - Operators can now reassign a ticket to any team, not just one they belong to. - The auto-refresh countdown badges (ticket view, operator queue) are now clickable — fetch immediately and reset the countdown. - All 7 "cyclical" intervals (3 browser refresh countdowns, the notification bell poll, and the 4 background scheduled commands) are now configurable from Admin > Konfiguracja instead of fixed in code. - Fixed: an operator viewing a ticket that's deleted or moved outside their team scope mid-session is now redirected to the operator queue instead of hitting an error. - Docs: README/ARCHITECTURE/CLAUDE/install/wiki updated for all of the above. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
7.5 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 tinkeragainst 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_errorsto 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 servicedesk ..., sudo docker exec servicedesk-servicedesk-1 ...).
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-servicedesk-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:(run from the repo root;sudo docker run --rm -v "$(pwd)/src":/app -w /app node:22 npm run buildnode_modulesalready exists, no install needed). Verify viapublic/build/manifest.jsonpicking 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 execruns asroot, notwww-data. Apache's worker processes run aswww-data; anything you run via a plaindocker exec(php artisan test,tinker,view:cache, etc.) runs asroot. On this NFS-backed mount, a file Blade compiles/caches while running asroot(storage/framework/views/*.php) can't later be overwritten bywww-datawhen a real request needs to recompile it (the source changed) — this surfaces in production as a 500 withtouch(): Utime failed: Operation not permitted. If you ranphp artisan test/tinker/any artisan command viadocker execin a session where you also edited Blade files afterward, finish withsudo docker exec servicedesk-servicedesk-1 php artisan view:clearto 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 need a host crontab entry
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 outside the container
calls php artisan schedule:run on a timer. As of 2026-07-23 this is
configured — root's crontab on the host runs, every minute:
* * * * * cd /mnt/rabbit-containers/servicedesk && docker compose exec -T servicedesk php artisan schedule:run >> /dev/null 2>&1
(sudo crontab -l -u root to inspect/edit — it previously did not exist at all,
which meant none of the four scheduled commands above had ever run
automatically; ask before changing this again, since removing it silently
breaks SLA checks, automation rules, IMAP fetching and AI ticket automation,
and confusingly not the IMAP feature alone if you're only debugging that one.)
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.
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()inapp/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/clientfirst-priority indefaultArea()if you add new roles/areas. - Mobile tables: don't rely on horizontal scroll alone for wide data tables.
The established pattern (
.table-cards-mobileinresources/css/app.css,@media (max-width:640px)) turns each<tr>into a card withdata-labelattributes 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).
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.