Shows a separator and centered "Clear selection" at the bottom of the dropdown
when any statuses are selected. Clearing shows all tickets regardless of status.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Status filtering now supports selecting multiple statuses via a dropdown with checkboxes.
Backend updated to accept comma-separated status values using Prisma `in` operator.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split the dense single-row filter bar into two rows: search + saved views on top,
filter selectors below. Fix CTI selectors to use design system tokens instead of
hardcoded dark classes, and upgrade the saved views button with an icon and badge count.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- .gitignore: add coverage/, .vscode/, .idea/
- .env.example files: add header comments clarifying production vs dev,
add SMTP vars to server dev template
- Validate SavedView filters on load with safeParse fallback
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extracted TicketFilters, BulkActions, and TicketListItem into
client/src/pages/tickets/. The main Tickets.tsx remains as the
page orchestrator with state management and pagination.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extracted TicketComments, TicketAuditLog, and TicketSidebar into
client/src/pages/ticket-detail/. The main TicketDetail.tsx remains
as the page orchestrator. Router import unchanged via index.ts
re-export.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaced loose Record<string, unknown> types on useCreateTicket,
useUpdateTicket, useCreateUser, useUpdateUser, useUpdateWebhook,
and useCreateSavedView with their corresponding shared schema types.
Fixed three type errors this surfaced at call sites.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
STATUS_LABELS was defined in the server, AUDIT_LABELS and AUDIT_COLORS
in the client. Both layers now import from a single shared source.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extracted idOrDisplayWhere() to eliminate the duplicated OR pattern
in getTicket, updateTicket, and commentService.addComment. The
exported findByIdOrDisplay() now uses it too.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Severity-to-color mapping was duplicated in SeverityBadge, Tickets,
and MyTickets. Consolidated into lib/severityColors.ts with both
solid-bg (for stripes) and badge-style (for badges) variants.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Type(categoryId), Item(typeId), Attachment(uploadedById), and
AuditLog(ticketId, createdAt) were missing indexes for their
primary query patterns.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both endpoints were authenticated but had no role check, allowing any
logged-in USER to view company-wide analytics and export all tickets.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Removes the blue tint from all dark-mode surfaces by switching CSS
variables to zinc-based neutrals, and replaces decorative blue classes
with indigo across buttons, focus rings, tabs, and links. Semantic blue
(severity badges, status badges, role badges, timeline markers) is
preserved.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
db push now runs with --accept-data-loss so the SERVICE enum-value
removal (rows already migrated by pre-push.sql) doesn't halt the boot.
Both Ticket and Comment also declare searchVector as
Unsupported("tsvector") so Prisma stops proposing to drop the columns
that post-push.sql manages — after this deploy, --accept-data-loss
becomes belt-and-suspenders rather than routinely required.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Dashboard now opts into `wide`, and the wide container scales from 1400
to 1800px at the 2xl breakpoint so content uses the extra room on big
monitors. Queue-load grid gains xl/2xl column counts for the new width.
Below 1536px nothing changes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Every AGENT now gets an auto-generated API key on creation, shown once
in a modal. AGENTs log in with password and authenticate to the API
with X-Api-Key. pre-push.sql defensively migrates any residual SERVICE
rows to AGENT before Prisma rewrites the enum. Goddard is no longer
baked into the seed — create agents via Admin → Users.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Command palette (cmd+K) with fuzzy nav, ticket search, people lookup
and action entries (new ticket, logout, show shortcuts). Opens from
keyboard or user dropdown.
Global keyboard shortcuts via a small useShortcut/useLeaderShortcut
hook: `?` help overlay, `c` new ticket, `g d|t|m|n|s` leader nav.
Tickets list: j/k cursor, Enter open, x toggle select. TicketDetail:
`e` edit, `r` focus comment composer. All guarded against firing
inside text fields.
@mention autocomplete in the comment composer (MentionTextarea) with
arrow-key nav and Tab/Enter insert. Rendered comments and audit log
rewrite @username tokens to links pointing at that user's assignee
filter; unknown usernames left as plain text.
Mobile sweep: TicketDetail sidebar stacks below content on <md,
Settings profile grid collapses to one column, admin tables get
horizontal scroll with a 640px min width, CTI 3-column grid stacks
vertically on <md, New ticket severity/assignee grid same.
PWA: manifest.webmanifest, SVG icon, minimal network-first service
worker for the app shell (never caches /api/*), registered in
production builds only. Theme-color meta + manifest link in index.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Attachments: multer-backed uploads with random-hex filenames,
streaming downloads with Content-Disposition, 25MB limit,
mimetype allowlist, audit entries, orphan cleanup on DB failure.
Full-text search: searchTicketIds + searchComments via raw SQL
ranked with ts_rank, composable filters via Prisma.sql/join,
hydrated with findMany and reordered via Map to preserve rank.
Pagination: listTicketsPaged returns {data,total,page,pageSize}
only when page/pageSize present (array response stays default,
so the Goddard n8n flow is unchanged).
Bulk actions: reassign/close/setSeverity/setStatus on POST /bulk,
writes one audit entry per ticket via createMany.
Analytics: summarize(window) runs 5 parallel groupBy + raw-SQL
queries for open-by-severity, status counts, queue load,
age buckets, percentile_cont median resolution hours.
CSV export streams matching tickets via res.write; saved views
CRUD with per-user ownership checks (403 cross-user, 404 missing).
Notifications: in-app Notification rows gated by prefs, email via
nodemailer (SMTP_HOST-gated, no-op when unset), outgoing webhooks
with HMAC-SHA256 signed POST and 3-retry exponential backoff.
Triggers wired into createTicket/updateTicket/addComment; mention
detection via parseMentions skips self-notify.
Infra: docker-compose uploads volume + SMTP env passthrough;
.env.example SMTP section.
43 server tests passing (attachment/webhook/notification/savedView
services covered; bulkAction covered in ticketService).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- New models: Attachment, Webhook, Notification, SavedView
- New fields: User.notificationPrefs (Json), indexes on Ticket
- post-push.sql manages the tsvector columns + GIN indexes + triggers for
FTS on Ticket (title/overview/displayId) and Comment (body); Prisma can't
express these
- package.json scripts: db:push and start:prod now chain `prisma db execute`
against post-push.sql after `prisma db push`
- db:migrate script removed — project uses push workflow, not migrations
- Shared Zod schemas: attachment (25MB limit + mimetype allowlist), savedView,
notification (prefs, mark-read, webhook CRUD)
- Shared type additions: Attachment, Notification, SavedView, Webhook,
PaginatedResponse<T>
- Test fixtures updated for the new User.notificationPrefs column
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Both forms use useForm with zodResolver against shared schemas
(loginSchema, createTicketSchema)
- Field-level errors rendered inline under inputs
- isSubmitting drives button disabled state
- NewTicket: severity registered with valueAsNumber; CTISelect wrapped in
nested Controllers (one per categoryId/typeId/itemId) since it controls
three form fields as a single compound input
- Admin forms stay on useState for now — they get redesigned with shadcn
dialogs in Phase 3, RHF migration lands with that
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- components.json, @/* path alias in tsconfig + vite config
- Tailwind config: CSS-variable-backed color tokens, animation plugin
- index.css: :root (light) and .dark token blocks (slate base) — currently
pinned to dark via class on <html> so visual appearance is unchanged
- src/lib/utils.ts: cn() helper (clsx + tailwind-merge)
- src/components/ui/: 16 primitives — button, input, label, textarea, badge,
avatar, separator, skeleton, dialog, dropdown-menu, select, tabs, tooltip,
sonner, alert-dialog, popover
- Nothing replaced yet; existing components still in place. Used in Phase 3.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- shared/schemas/: move Zod schemas out of routes so client + server share them
- shared/types.ts: inferred types and enums for cross-package use
- server tsconfig rootDir raised to ".." so shared/ compiles in-tree
- server/src/services/: ticket, comment, cti, user, auth, notification (stub), search (stub)
- Routes thinned to validate-delegate-return; business logic now testable in isolation
- server/src/lib/httpError.ts: typed HttpError replaces ad-hoc throw shapes
- server/src/lib/logger.ts: pino structured logging replaces console.log
- autoClose job delegates to ticketService.closeStale()
- express-rate-limit on /api/auth/login (10 / 15min / IP)
- vitest + vitest-mock-extended; 20 service-level tests cover auth, ticket, comment, user flows
- CI: lint + test jobs before docker builds
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Each comment is a bordered card with a distinct header bar (author name,
clickable relative timestamp, hover-to-reveal delete) and a body section
- Subtle spine line connects comments in the avatar column
- Composer matches card style: same header bar for Write/Preview tabs,
transparent textarea inside, submit row with border-top
- Comment timestamps default to relative, click to toggle absolute
(mirrors sidebar date toggle pattern)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Routes and links now use /:id (e.g. /V675409888) instead of /tickets/:id.
API calls are unaffected as they go through /api/tickets/.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Delete the now-unused SidebarField component from TicketDetail.tsx
- Add typecheck-client CI job that runs tsc --noEmit on the client before
the Docker build, so TypeScript errors surface fast with a clear message
- build-client now depends on typecheck-client passing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Defaults to relative time (e.g. '5 hours ago'); clicking switches to
absolute timestamp and back.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Status and Severity now match CTI style (full-width button, hover bg, no chevron)
- Remove 'Change routing' hint text from CTI block
- Replace Assignee dropdown with clickable block that opens a modal picker with avatars
- Add Assignee modal consistent with Status/Severity modal pattern
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rename 'Details' card to 'Ticket Summary'
- Replace status/severity dropdowns with badge displays that open small modal pickers on click
- Show Category, Type, Issue as separate labeled rows that together act as one clickable unit opening the routing modal
- Reorganize into sections: status/severity, CTI routing, dates (created/modified/resolved), people (assignee + requester)
- Add Requester field showing the ticket creator with avatar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- My Tickets: exclude RESOLVED and CLOSED, show active tickets only
- Queue filter: cascading Category > Type > Item picker — each leaf is
a distinct queue (e.g. TheWrightServer > Automation > Backup vs Sync)
- Server: support typeId and itemId as ticket list filter params
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dark UI across all pages and components (gray-950/900/800 palette)
- New Ticket is now a centered modal (triggered from sidebar), not a separate page
- Add USER role: view and comment only; AGENT and SERVICE can create/edit tickets
- Only admins can set ticket status to CLOSED (enforced server + UI)
- Add My Tickets page (/my-tickets) showing tickets assigned to current user
- Add queue (category) filter to Dashboard
- Audit log entries are clickable to expand detail; comment body shown as markdown
- Resolved date now includes time (HH:mm) in ticket sidebar
- Store comment body in audit log detail for COMMENT_ADDED and COMMENT_DELETED
- Clarify role descriptions in Admin Users modal
- Remove CI/CD section from README; add full API reference documentation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
prisma db push cannot add a non-nullable column to an existing table
without a database-level default. Using a PostgreSQL expression to
generate V + 9 random digits as the fallback default.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Tickets get a random display ID (V + 9 digits, e.g. V325813929)
- Ticket detail page has Overview / Comments / Audit Log tabs
- Audit log records every action (create, status, assignee, severity,
reroute, title/overview edit, comment add/delete) with who and when
- Comments redesigned: avatar (initials + color), markdown rendering
via react-markdown + remark-gfm, Write/Preview toggle
- Dashboard shows displayId and assignee avatar
- URLs now use displayId (/tickets/V325813929)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eliminates the need to generate and commit migration files locally
before first deploy. Schema is synced directly from schema.prisma
on container start.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>