Skip to content

Support Tickets

As of: 2026-10-02 · Wave 1

With support tickets, every signed-in user can report a bug, a wish, a question or praise straight from LLARS — with screenshots, files and a voice note. LLARS automatically attaches the page and the version the ticket was created on. Admins work through tickets on a board, and the operator answers them with the help of AI agents that document every change in the ticket with before and after screenshots.

Every ticket has an identifier such as T-12. It is never reused and is easy to mention in conversations and emails.


Creating a ticket

The "Create ticket" button sits in the top-right app bar, to the left of your name. The keyboard shortcut ⌘⇧F (Mac) or Ctrl+Shift+F (Windows, Linux) is quicker and works from any page. The "My tickets" link at the bottom left of the dialog opens your previous tickets and replies. An unfinished draft is kept when you leave.

In the dialog you fill in:

Field Note
Title One sentence describing the issue (at most 200 characters).
Description What you did, what happened and what you expected instead.
Category Bug · Wish · Question · Praise
Urgency Low · Medium · High · Blocker (blocker = you cannot continue)
Attachments Images, PDFs, text and audio files, at most 5 at a time
Voice note Up to 2 minutes of speech instead of typing

Adding attachments — four ways lead to the same result:

  • Choose a file with the button in the attachment area,
  • Paste with ⌘V or Ctrl+V, for example a screenshot from the clipboard,
  • Drag and drop a file onto the form,
  • "Capture page": LLARS takes a picture of the current page without the dialog, hints or notifications and attaches it as screenshot-….png. The image only leaves your browser when you submit the ticket.

Voice note: one click starts the recording (the browser asks once for microphone access), a second click stops it; it stops on its own after 2 minutes. If a transcription model is configured on the server, the note later also appears as text in the ticket. Without a model it remains playable.

Context sent along: when the dialog opens, LLARS records the current page (route), the LLARS version, the window size, the browser, the language and the colour scheme. You can expand these details in the dialog. They help to reproduce the problem exactly where it happened.

Submit with the button or with ⌘↩ or Ctrl+Enter. If you cancel, your draft is kept; only a successfully submitted ticket clears the form. After submitting, LLARS confirms the identifier: "Ticket T-12 has arrived".

Editing images

A click on the preview of an attached image opens it full screen. There you can edit it before you send the ticket — for example to hide names or to point at the spot that matters.

Tool What it does
Pixelate Drag a rectangle; the area is pixelated irreversibly. The yellow frame only shows where you have already pixelated and does not end up in the image.
Pen Draw freehand with the mouse or your finger.
Rectangle Frame a spot.
Arrow Drag from the start to the tip.
Text Click where it should go, type and confirm with Enter.
Eraser Removes a marking (pen, rectangle, arrow, text). Pixelation and cropping are taken back with "Undo".
Crop Drag the area to keep and confirm with "Apply crop"; you then keep working inside that area.

For pen, rectangle, arrow and text you pick one of four colors. Undo and Redo (also ⌘Z or Ctrl+Z and ⌘⇧Z or Ctrl+Y) cover every step, including the crop. Reset all brings back the originally attached image, even if you have already edited and saved it before.

Save puts the result into the attachment. The preview then carries the label "edited", and the number of attachments stays the same. If the image is larger than 10 MB as a PNG, LLARS saves it as a JPEG and tells you so. Cancel or Escape discards the changes since opening; the "Create ticket" dialog stays open, and while the editor is open ⌘↩ sends nothing. Editing happens entirely in your browser: nothing leaves your computer until you click "Send ticket".


My tickets

My tickets (/tickets, reachable through the home tile, the menu entry at the top right and the link in the create dialog) lists every ticket you created or are responsible for. The badge on the "Create ticket" app-bar button counts unseen changes to your tickets.

A click opens the ticket detail (/tickets/T-12):

The fixed bar at the bottom lets you browse with Previous and Next in the order of the list you came from; the ← and → keys do the same while you are not typing or using a dialog. Back to list returns to that list; for a directly opened ticket, LLARS uses the most recently updated tickets you can access.

  • The timeline shows all events and comments in chronological order, with previews of the attachments. Images open in a large view, voice notes can be played.
  • At the bottom you write a comment, attachments included.
  • You can confirm an open ticket as done. While it is Done, you can still reply or reopen it with a short reason. After 14 days without a new human comment since completion, the system closes it automatically. The detail view shows the expected closing date.

Changes appear immediately, without reloading the page.

What the statuses mean

Status Meaning
New The ticket has arrived and has not been picked up yet.
In progress Someone is working on it; the comment says what is being built.
Needs reply The ticket is waiting for you — usually for an answer or a decision.
Done Implemented and documented with screenshots. You can still reply or reopen it. After 14 days without a human comment, it becomes “Closed” automatically.
Rejected Will not be implemented; the last comment gives the reason.
Closed Archived completion. You can read the ticket but cannot comment or reopen it. Create a new ticket for a new request. The support team can reopen it if needed.

Tickets are never deleted. Rejected and closed tickets stay readable too. An automatic closure does not trigger an unseen marker.


The ticket board (admin)

Admins open the board through the home tile "Support Tickets" (/admin/tickets).

  • Three views: List (sortable by number, title, category, urgency, status, assignee and last update), Kanban (one column per working status; dragging a card changes its status, and the card menu "Set status" does the same from the keyboard) and Cards (with cover image and excerpt). The browser remembers the chosen view.
  • Closed appears in the list and cards with its own counter and filter, but has no Kanban column. Only the system sets this status.
  • Filters and counters: status, category, assignee ("assigned to me", "nobody", a person) and full-text search in title and description. The counters show how many tickets have each status. Rejected test tickets with [SMOKE …] or [E2E …] in the title are hidden and excluded from the counters by default. "Show test runs" brings them back; open test tickets always remain visible.
  • Assignment: exactly one person is responsible. "Assign to me", pick a person or remove the assignment — only members of the support team (permission feature:tickets:manage) can be assigned. The assignee sees the ticket and may change its status, urgency and category.
  • Status, urgency and category are changed in the detail view. Every change shows up in the timeline afterwards, optionally with a comment.
  • "Decision needed": a dot in front of the identifier and a dedicated filter mark tickets where a human has to decide something; the detail view shows the sentence in question at the top. As soon as a human acts on the ticket (comment, status change, attachment), the marker disappears on its own.
  • Cover image: an image attachment can serve as the card's cover.

Changes by other admins and by the AI agent appear live; in addition, the board polls every 30 seconds and whenever you return to the tab.


For developers

Data model

All tables are created via create_all() in app/db/models/support_ticket.py. Users are stored as usernames without foreign keys so that the timeline survives the deletion of an account.

Table Content
support_tickets title, description, category, priority, status, creator_username, assignee_username, context_json, decision_needed/decision_text, cover_attachment_id, timestamps incl. resolved_at
support_ticket_attachments kind (screenshot/audio/file), sanitised filename, server-determined mime_type, size_bytes, sha256, content as LONGBLOB (deferred), origin (creator/admin/agent), transcript fields, optional comment_id
support_ticket_comments author_username, author_type (human/agent/system), body (1–20,000 characters)
support_ticket_events The timeline: actor_username, actor_type, action (created, commented, attached, status_changed, priority_changed, category_changed, assigned, decision_flagged, decision_cleared, cover_changed, transcribed, agent_accessed), details_json

Values in the database and the API are English; the UI translates them via i18n: status = new, in_progress, needs_reply, done, rejected, closed · category = bug, feature, question, praise · priority = low, medium, high, blocker.

API

All routes live under /api/tickets and accept a session (bearer token) or the system admin API key in the X-API-Key header. Responses have the form {"success": true, …}, errors {"success": false, "error": "…"}. The identifier <ref> may be T-12, t-12 or 12.

Method & path Permission Purpose
POST /api/tickets view Create: JSON without files or multipart with title, description, category, priority, context (JSON string) and up to 5 × files → 201
GET /api/tickets view List with status, category, priority, assignee=me\|none\|<user>, q, scope=mine\|all, decision=1, page, per_page (≤ 100), sort=updated_desc\|created_desc\|priority, optional hide_tests=1 (hide rejected test tickets only)
GET /api/tickets/stats view Counts per status, own replies needed and open tickets, decision_needed
GET /api/tickets/assignees manage Possible assignees
GET /api/tickets/<ref> view Detail with attachments, comments, events
PATCH /api/tickets/<ref> view status, priority, category, assignee, decision_needed, decision_text, cover_attachment_id, optional comment
POST /api/tickets/<ref>/comments view Comment as JSON {body} or multipart with files → 201
POST /api/tickets/<ref>/attachments view Attachments (files, up to 5) → 201
GET /api/tickets/<ref>/attachments/<id> view File; ?inline=1 only for images, audio and PDF
GET /api/tickets/<ref>/events view Timeline

Examples with the local system key (the value lives in .env as SYSTEM_ADMIN_API_KEY and does not belong in anyone else's shell history):

KEY=<from .env>   # SYSTEM_ADMIN_API_KEY
BASE=http://localhost:55080

# Open tickets that need a decision
curl -s "$BASE/api/tickets?scope=all&decision=1" -H "X-API-Key: $KEY" | jq '.tickets[] | {ref, title, status}'

# One ticket with its timeline
curl -s "$BASE/api/tickets/T-12" -H "X-API-Key: $KEY" | jq '.ticket | {ref, status, events: (.events | length)}'

# Comment with a screenshot
curl -s -X POST "$BASE/api/tickets/T-12/comments" -H "X-API-Key: $KEY" \
  -F "body=The button is back, see the image." -F "files=@t12-nachher-hell.png;type=image/png"

# Set the status, with a comment
curl -s -X PATCH "$BASE/api/tickets/T-12" -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"status": "in_progress", "comment": "Being built."}'

A call with the system key counts as an AI agent: comments and events carry author_type/actor_type = agent, and a read of the detail writes the event agent_accessed at most once per ticket and hour.

Permissions

Permission Roles Allows
feature:tickets:view researcher, evaluator, chatbot_manager, ijcai_reviewer Create tickets, see and comment on own and assigned tickets; as the creator, confirm completion (done) or reopen with a comment (new)
feature:tickets:manage admin See and change all tickets, assign, set "decision needed", board

Someone else's ticket answers with 404, not 403, so ticket numbers cannot be probed. TicketService in app/services/tickets/ enforces the finer rules.

Live updates (Socket.IO)

The client emits tickets:join and joins the room tickets_user_<username>, with manage also tickets_admin; tickets:leave leaves them. The server emits tickets:created and tickets:updated with {ticket, event} to the admin board, the creator and the assignee. As a fallback the frontend polls every 30 seconds and when the tab becomes visible again.

Transcription

Recording always works; transcription only runs when the environment variable TICKET_TRANSCRIPTION_MODEL is set. It names an OpenAI-compatible /audio/transcriptions model in the same id format as the LLM models and runs through LLMClientFactory. An audio attachment's status goes from pending via running to done or failed; without a model it is unavailable.

Limits and allowlist

Limit Value
File at most 10 MiB
Files per upload 5
Files per ticket 30
Title 200 characters
Description, comment 20,000 characters

Allowed are PNG, JPEG, WebP, GIF, PDF, plain text, Markdown, CSV, JSON and audio as WebM, Ogg, MP4/M4A and WAV. The server identifies images and PDFs by their first bytes, never by what the browser claims; SVG and HTML are forbidden. Downloads carry X-Content-Type-Options: nosniff, Content-Security-Policy: sandbox and Cache-Control: private, no-store.

Files in the repository

app/db/models/support_ticket.py              # tables
app/routes/tickets/                          # blueprint /api/tickets
app/services/tickets/                        # TicketService, validation, transcription
app/socketio_handlers/events_tickets.py      # tickets:join/leave
llars-frontend/src/lib/tickets.js            # constants, ref parser, ticketsApi
llars-frontend/src/components/tickets/       # dialog, timeline, attachments, board parts
llars-frontend/src/views/Tickets/            # my tickets, detail, admin board
.claude/skills/tickets/SKILL.md              # skill /tickets
scripts/agenten/                             # tickets.py, screenshot.mjs, README.md
scripts/ci/smoke_test_tickets.sh             # smoke round trip (SMOKE_TICKETS)

For AI agents

The operator works through tickets with the Claude Code skill /tickets (.claude/skills/tickets/SKILL.md). The flow: read everything (description, images, transcripts, comments), accept the ticket with a comment, take before screenshots, settle design questions with proposals, let agents build, verify the work with tests, deploy to the test instance, take after screenshots, comment with evidence and set the status. Comments are written in the ticket's language, in short complete sentences, and name the attached images.

scripts/agenten/tickets.py — the command line for the API, using only the Python standard library:

python3 scripts/agenten/tickets.py --env local list
python3 scripts/agenten/tickets.py --env local show T-12 --download <scratchpad>/tickets/T-12
python3 scripts/agenten/tickets.py --env local status T-12 in_progress --comment "Being built."
python3 scripts/agenten/tickets.py --env local comment T-12 "…" --file t12-nachher-hell-1440.png
python3 scripts/agenten/tickets.py --env local decision T-12 "Please choose A or B."

Further commands are attach, assign and classify. --env selects the instance (local, dev, prod) and with it the key variable in .env (SYSTEM_ADMIN_API_KEY, DEV_ADMIN_API_KEY, PRODUCTION_ADMIN_API_KEY). The script never prints the key and checks files, sizes and text lengths before the first network call.

scripts/agenten/screenshot.mjs — evidence screenshots of a signed-in page with Playwright, light or dark, desktop or phone, optionally with numbered red frames:

node scripts/agenten/screenshot.mjs --env local --user evaluator --path /tickets \
  --dark --mobile --out <scratchpad>/t12-vorher-dunkel-390.png

File names follow the skill's German convention: t12-vorher-… (before) and t12-nachher-<instance>-… (after), plus hell/dunkel (light/dark) and the width, e.g. t12-nachher-dev-dunkel-390.png. scripts/agenten/README.md documents both tools in full. The smoke test scripts/ci/smoke_test_tickets.sh runs a round trip through the API after every deploy; its ticket carries [SMOKE …] in the title and ends as "Rejected", because there is no way to delete tickets. The admin board hides these completed test runs by default. The API list without hide_tests=1 and "My tickets" still include them. Test runs never trigger the beige unseen marker or a snackbar.


Planned

These items are deliberately not part of wave 1:

  • Email notification when a reply is needed and when a ticket is done.
  • Automatic categorisation of new tickets by a language model.

There is no delete function for tickets, and none is planned.