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.