Commit Graph

130 Commits

Author SHA1 Message Date
jared 6c491c1baa Fix close-ticket UX, add cmd palette hint, breadcrumb, image lightbox
- ticket.js: status change requiring a comment now shows an inline
  modal with a textarea — comment is actually posted before the status
  changes, instead of just warning the user and changing anyway
- layout_header.php: add ⌘K button in header so users can discover
  the command palette; also removes inline onclick in favor of JS
  (CSP-safe via nonce script block already present)
- TicketView.php: upgrade breadcrumb to lt-breadcrumb markup with
  ticket title preview (truncated at 45 chars) and aria-current
- ticket.js + ticket.css: image attachments now render as clickable
  thumbnails (3rem×3rem) that open in lt.lightbox; non-image files
  keep the icon display unchanged

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 12:15:40 -04:00
jared bc88ba3612 Fix notifications 500 (audit_id column), smart resolution time units
- notifications.php: audit_log PK is audit_id not log_id; alias all
  three queries with audit_id AS log_id to fix 500 error
- DashboardView: avg resolution time now picks best unit automatically
  (min < 1h, hr < 48h, days < 14d, wks otherwise) with full hours
  shown in title tooltip; adds lt-stat-unit CSS for the suffix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 11:32:02 -04:00
jared df6c4de196 Fix notification comment query, status title, and is-hidden visibility
notifications.php: comment notifications never fired because the query
used action_type='comment'/entity_type='ticket' but logCommentCreate
logs action_type='create'/entity_type='comment'. Fix query to match
actual log format and extract ticket_id from details JSON.

notifications.php: status change notification titles always showed
"? → ?" because code read details.old_value/new_value but logTicketUpdate
stores the delta as {"status": {"from": ..., "to": ...}}.

base.css: move .is-hidden to base.css (global) — it was only defined in
ticket.css, so on the dashboard the ticket-preview popup had no hide
rule applied and was visible in the DOM at all times.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 10:47:39 -04:00
jared 914c33ecf3 Fix CSP-blocked chart scripts, undefined CSS classes, and double-firing click handlers
- Add nonce to charts and ticket-preview drawer inline <script> blocks in
  DashboardView.php (both were CSP-blocked — charts never rendered)
- Add .lt-modal-xs (280px) to base.css — used by quickStatus/quickAssign
  modals but was undefined, causing them to use full modal width
- Fix showConfirmModal in utils.js: class="text-center" → "lt-text-center"
  (undefined class); escape newlines as <br> so multi-line messages render
- Remove duplicate click-handler cases from DashboardView.php inline script
  that were already handled by dashboard.js, preventing double-firing
  (export-tickets, open-settings, remove-filter, etc. were all called twice)
- Fix manual-refresh action to use lt.autoRefresh.now() instead of bare
  window.location.reload() so modal/focus guards are respected

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 23:00:35 -04:00
jared b7b6884bb0 fix: add missing CSS classes + clean up remaining inline styles
- Add .lt-modal-sm (max 360px) and .lt-modal-header--danger variant used
  in JS-generated bulk delete confirmation modal (no CSS = unstyled header)
- Add .lt-badge-sm for compact inline badges (comment counts, group tags)
- Add .lt-kv-row { display:contents } with .lt-kv-label/.lt-kv-value rules
  (was missing from previous commit — added in base.css)
- Replace style="text-align:center" with .lt-text-center in JS modal body
- Replace style="flex-direction:column" with .lt-flex-col on .lt-btn-group

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 22:50:13 -04:00
jared 54887ffa24 fix: kanban not loading on refresh + modal horizontal scroll + lt-kv-row CSS
Kanban restore bug:
- set-view-mode click handler called populateKanbanCards() directly but never
  called setViewMode(), so ticketViewMode was never saved to localStorage
- DOMContentLoaded restore checked ticketViewMode (never written) — it should
  check lt_activeTab_<path> which lt.tabs.init() actually saves
- Fix: delegate to setViewMode() from the click handler; DOMContentLoaded
  reads lt_activeTab_<path> and calls populateKanbanCards() when tab-kanban

Settings modal horizontal scroll:
- .lt-modal-body was missing overflow-x: hidden; content wider than the modal
  (e.g. kbd elements with white-space: nowrap) caused horizontal scrollbar
- Added overflow-x: hidden + min-width: 0 to .lt-modal-body

Missing lt-kv-row / lt-kv-label / lt-kv-value CSS:
- These classes were used in TicketView, DashboardView, admin views but had
  no primary CSS rules (only a light-theme color override existed)
- Without rules, lt-kv-row divs were block-level grid children consuming one
  grid cell each, making lt-kv-label/value stack inside wrong columns
- Added display:contents on lt-kv-row so children participate directly in
  the lt-kv-grid 2-column grid; lt-kv-label/value get padding, border, and
  min-width:0 + overflow-wrap:break-word to prevent grid column blowout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 22:45:43 -04:00
jared 1ab374531c fix: avatar image overlays initials, chart canvas responsive sizing
Avatar bug:
- base.css: .lt-avatar now position:relative; img is position:absolute inset:0
  so a loaded image covers the initials span (fixes img+initials shown together)
- base.css: .lt-avatar img.lt-avatar-img-err { display:none } — CSS hook for error state
- layout_footer.php: capture-phase error event delegation on .lt-avatar imgs
  replaces blocked inline onerror handlers (CSP has no unsafe-inline in script-src)

Chart bug:
- DashboardView: replaced display:flex section-body containers with a
  position:relative; width:100%; height:170px div wrapper for each canvas
  (Chart.js responsive:true reads parentNode dimensions; flex containers
  give canvas zero intrinsic width causing 0×0 render = empty charts)
- Removed has-lt-overlay from chart frames (no overlay div was injected)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 18:25:27 -04:00
jared bfe00ea0f6 fix: add lt-toggle--sm CSS variant for compact toggle switches in comment bar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 17:46:31 -04:00
jared 04b019a8e1 feat: Chart.js donut/bar charts, Flatpickr dates, skeleton loaders, CSP update
- DashboardView: Charts row with 3 panels (priority donut, status donut, category bar)
  using Chart.js from CDN; data passed inline from PHP stats; TDS color palette
- DashboardView: Flatpickr date picker on advanced search date fields with TDS theme overrides
- dashboard.js: showTableSkeleton() shows lt-skeleton-row during filter-triggered reloads
  and auto-refresh; called before all location.reload() with delay
- dashboard.css: Flatpickr TDS theme overrides (dark BG, monospace font, TDS accent colors)
- SecurityHeadersMiddleware: Added cdn.jsdelivr.net to script-src and style-src CSP
  to allow Chart.js and Flatpickr from CDN

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 17:45:02 -04:00
jared 6727aeea29 feat: saved filter pills, mention autocomplete CSS, tooltips on dashboard table
- Dashboard: saved filter pills row above active filters bar — loads from API,
  click applies criteria as URL params, hidden when no saved filters exist
- ticket.css: add TDS-styled CSS for @mention autocomplete dropdown (was unstyled)
- Dashboard table: data-tooltip on Title and Assigned To columns for truncated text
  (lt.tooltip.init() auto-called by lt.init(), zero extra JS needed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:06:46 -04:00
jared 0d8edc9d34 feat: trend dots on stat cards, team workload panel, stat model improvement
- Dashboard stat cards now show lt-dot trend indicators (up/warn/idle) based on
  created_today vs closed_today flow — no extra DB query needed
- Add collapsible Team Workload panel showing assignee open ticket counts with
  progress bars (green/cyan/red by load), avatar, and name
- StatsModel.getTicketsByAssignee() now returns proper objects with user_id,
  display_name, open_count (was name-keyed flat array); limit raised to 8

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:04:41 -04:00
jared fca4896e0d fix: watcher avatars, dependency TDS styling, asset versions, nav dropdown light theme
- watch_ticket.php GET now returns watcher list (up to 6 users) for avatar group
- TicketView: watcher avatar group rendered next to WATCH button, refreshes on toggle
- Rewrite renderDependencies/renderDependents to use TDS lt-kv-grid/lt-badge/lt-btn classes
- renderDependencies: show lt-alert--warning blocker banner when blocked_by has open tickets
- Fix ALL hardcoded ?v=20260327 asset version strings in CreateTicketView + all admin views
- base.css: fix .lt-nav-dropdown-menu hardcoded background → var(--bg-overlay)
- base.css: add light-theme overrides for nav dropdown menu (background, links, hover)
- ticket.css: add .lt-avatar-group and .lt-avatar--overflow styles for watcher display

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:02:30 -04:00
jared 85afec64ac Add responsive .lt-main.lt-container overrides to match production base.css
Production base.css has per-breakpoint .lt-main.lt-container rules that
explicitly set padding-top with tighter spacing at SM/XS viewports. Adding
these to beta to match — ensures header clearance is bulletproof at all sizes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 11:13:16 -04:00
jared 0eab5d40e6 Restore .lt-main.lt-container combined selector — proper cascade fix
The TDS v1.2 sync removed the .lt-main.lt-container combined selector that
was already in the project's base.css. That selector has specificity (0,2,0)
vs single-class (0,1,0), so it always wins over .lt-container padding
shorthand at every breakpoint without needing per-breakpoint overrides.

Also restored flex:1, width:100%, min-width:0 on .lt-main that were dropped.
Removed the incorrect per-breakpoint .lt-main and #main-content hacks added
today which were the wrong approach to the same problem.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 17:49:00 -04:00
jared 3cfe46050b Fix header overlap with ID selector — unambiguous highest specificity
Use #main-content (specificity 1,0,0,0) to set padding-top at each breakpoint.
This cannot be overridden by any class-based rule regardless of cascade order,
permanently fixing the fixed header overlapping page content.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 17:45:14 -04:00
jared 6102985f92 Fix header overlap at all breakpoints — restore lt-main padding-top
Every media query that overrides .lt-container { padding } with a shorthand
was clobbering .lt-main { padding-top } because both selectors have equal
specificity and the container rule came later in the file. Added .lt-main
padding-top restores after each affected breakpoint (LG 1024-1279px, MD
768-1023px, 1920px+). The laptop range (LG) was the likely culprit on desktop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 17:41:33 -04:00
jared e91709798b Fix header overlapping content at mobile breakpoints
In the SM (≤767px) and XS (≤479px) media queries, .lt-container { padding }
shorthand appeared after .lt-main { padding-top } with equal specificity,
causing the shorthand to clobber the header-clearance padding-top. Swap order
so .lt-main always wins.

Also remove redundant lt-scanlines div — body::before in base.css already
renders the scanline overlay globally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 17:19:22 -04:00
jared cfdc9e0f37 Sync TDS v1.2 additions: scanlines, cursor, radar, display-field, VT323
- Sync base.css + base.js from web_template (adds lt-scanlines,
  lt-cursor, lt-radar, lt-display-field, --font-crt/VT323 token)
- Add VT323 to Google Fonts link in layout_header.php
- Add lt-scanlines to <body> — CRT scanline overlay, light-mode suppressed
- Replace custom .editable-metadata:disabled CSS override in ticket.css
  with the canonical .lt-display-field class from base.css
- Switch Priority/Category/Type/Visibility selects and visibility-group
  checkboxes in TicketView.php from disabled attribute to lt-display-field
- Update toggleEditMode() in ticket.js to add/remove lt-display-field
  instead of toggling the disabled attribute

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 16:55:12 -04:00
jared fdc6d3d463 Fix ASCII art alignment, readonly input opacity, api key visibility
Use white-space:pre-wrap on description view div so newlines and multiple
spaces are preserved natively — no <br> replacement, ASCII art aligns
correctly since body is already monospace (JetBrains Mono).

Override opacity:1 on readonly API key input so generated keys are fully
readable instead of being faded to 0.45 by base.css [readonly] rule.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:43:18 -04:00
jared 72d5061867 Fix description line breaks and disabled-field readability
Ticket descriptions are plain text — renderDescriptionView() now always
uses nl2br instead of parseMarkdown(), preventing markdown from mangling
single newlines into run-on paragraphs.

Override base.css opacity:0.45 on disabled .editable-metadata selects
(Priority, Category, Type) so they remain legible at full contrast on
dark/OLED screens in read mode.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:36:10 -04:00
jared 1d721eecb4 fix: description unreadable in dark mode / OLED — swap disabled textarea for lt-markdown div
Root cause: disabled textarea gets opacity:0.45 + color:var(--text-muted) from
base.css, making it near-invisible on OLED (true-black background).

Fix:
- TicketView: add #ticketDescriptionView (div.lt-markdown) alongside the textarea;
  textarea is now hidden by default (style="display:none"), view div is shown
- ticket.js: renderDescriptionView() renders raw text via parseMarkdown() or nl2br;
  showDescriptionView() / showDescriptionEdit() swap between them;
  toggleEditMode() calls showDescriptionEdit() when entering edit, and
  renderDescriptionView() + showDescriptionView() when returning to read mode
- ticket.css: .ticket-description-view sets full-contrast text-primary/secondary
  colors, min-height, and line-height for comfortable reading

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 18:10:39 -04:00
jared 2e450dc01d Apply web_template gap analysis improvements (P1-P3)
P1-A: Fix CSP - add fonts.googleapis.com to style-src, fonts.gstatic.com to font-src
P1-B: CSRF token rotation - add rotateToken() to CsrfMiddleware; bootstrap.php rotates
      after successful validation and stores in $GLOBALS['_new_csrf_token']; add
      apiRespond() helper to append token to responses; lt.api interceptor in
      layout_footer.php auto-updates window.CSRF_TOKEN from responses
P1-C: Styled 403/404 error views with TDS layout instead of raw text; index.php now
      uses requireAdmin() helper eliminating 7 duplicated guard blocks (P3-D)
P2-A: Remove duplicate JS-generated keyboard help modal from keyboard-shortcuts.js;
      '?' key now routes to static #lt-keys-help modal in footer
P2-B: Asset versioning driven by config ASSET_VERSION key; base.css and base.js get
      ?v= cache-busting in layout_header.php
P2-C: Add data-theme="dark" to <html> tag to prevent FOUC on light-mode users
P2-E: Escape status value in dashboard.js hover preview class attribute via lt.escHtml()
P2-F: Replace bespoke showLoadingOverlay() with lt-spinner / lt-loading-text from
      base.css; add .lt-loading-overlay wrapper CSS to dashboard.css
P2-G: Add keyboard-shortcuts.js to all 7 admin views so J/K nav and ? help work
P3-A: APP_NAME, APP_SUBTITLE, APP_VERSION driven from config.php; layout header/footer
      use config values instead of hardcoded strings
P3-G: Replace custom initTableSorting() with lt.sortTable.init() which manages aria-sort

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 17:02:40 -04:00
jared d8e6dcf7fa fix: CSS nesting conflict, dashboard.js dead code removal, admin view escaping
CSS:
- ticket.css: use combined .comment.thread-depth-N selectors to resolve the
  margin-left conflict between .comment-reply and .thread-depth-N classes

dashboard.js:
- Remove legacy initStatusFilter() (superseded by TDS v1.2 sidebar filters)
- Remove initTableSorting() call (client-side sort conflicts with server ?sort=)
- Remove quickSave() + saveTicket() (old hamburger-menu ticket page functions)
- Remove global loadTemplate() (duplicate of IIFE-scoped version in CreateTicketView)
- Remove generateSkeletonRows/Comments/Stats helpers (never called, used
  unregistered CSS class names like .skeleton-row-tr)
- Remove "force dark mode" lines that overrode the user theme preference
- Fix non-TDS CSS classes in modal templates: text-center → style, text-green →
  lt-text-cyan, mb-half → lt-mb-xs, modal-warning-text → lt-text-danger

Admin views:
- RecurringTicketsView: replace innerHTML += loop with createElement/appendChild
  (avoids serial DOM re-parsing on each iteration)
- AuditLogView: add htmlspecialchars() to action_type option values (consistency)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 21:34:34 -04:00
jared 18bf1fde0e feat: LDAP avatar support via lldap
- Create tinker-tickets service account in lldap (lldap_strict_readonly)
- Add /api/user_avatar.php: binds to lldap, fetches avatar attribute,
  caches JPEG to uploads/avatars/, returns 404 sentinel for missing photos
- Install php8.2-ldap on LXC 132 (beta) and LXC coding server
- Update layout_header.php: show lt-avatar with photo overlay + initials fallback
- Update TicketView.php: comment avatars use photo overlay pattern
- Add .lt-avatar-img / .lt-avatar-initials CSS for photo-over-initials layout
- Add LDAP_* config keys to config.php and .env.example

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 20:47:08 -04:00
jared 3bb4792635 Fix header overlap, is-hidden missing globally, and CreateTicketView CSS
- base.css: add .lt-main.lt-container combined selector (specificity 0,2,0)
  to prevent responsive .lt-container padding shorthand from overriding
  the fixed-header clearance padding-top — affected all viewports < 1280px
- base.css: add .is-hidden { display: none !important } globally; it was
  only defined in ticket.css so dashboard ticketPreview popup rendered
  as a green box at 0,0 on page load instead of being hidden
- CreateTicketView.php: add dashboard.css to pageStyles so create-ticket-
  meta-grid, lt-form-hint, visibility-groups-list, duplicate-list classes
  are available (they were undefined when only ticket.css was loaded)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 13:30:00 -04:00
jared b42597c927 Fix CSS variables, missing utility classes, API hardening, and audit log UX
- base.css: add --lt-border/--lt-surface aliases so dashboard.css respects
  theme instead of using hardcoded fallback colors
- base.css: add lt-select-sm/lt-input-sm compact size variants (used in 15+
  places), lt-msg-danger alias for lt-msg-error, lt-form-hint--warn,
  lt-font-mono utility class
- audit_log.php: cap ?limit= at 500 to prevent DoS via oversized queries
- ApiKeysView.php: replace deprecated execCommand('copy') with lt.copy();
  add integer casts on api_key_id in id attr and data-id
- AuditLogView.php: rebuild pagination with windowed prev/next/ellipsis
  pattern matching DashboardView; integer cast on user_id select option

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 13:22:12 -04:00
jared e721b33911 Align UI with web_template TDS v1.2 standards
- Replace lt-chip priority badges with lt-badge lt-badge-p[1-4] across
  DashboardView, TemplatesView (matches web_template sticky table pattern)
- Add lt-theme-btn theme toggle to header-right; wire lt.theme.toggle()
- Replace ASCII art empty state with lt-empty-state component in dashboard
- Standardize tab wrapper lt-tabs → lt-tab-bar in Dashboard and TicketView
- Add missing lt-keys-help modal to layout_footer (fixes ? key doing nothing)
- Add lt-cmd-overlay command palette container + lt.cmdPalette.init() nav
- Add .lt-timeline-action CSS rule (used in TicketView, was undefined)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 13:06:40 -04:00
jared d7775e62ec Fix layout regressions, nav drawer structure, and security issues
- base.css: add width:100%+min-width:0 to .lt-main so flex column body
  doesn't shrink content due to margin:0 auto from .lt-container
- layout_header.php: restructure mobile nav drawer to match web_template
  exactly (nav-drawer-links nav, direct <a> links, section div, no ul/li
  wrapper, overlay after drawer); fix lt-nav-overlay id mismatch with
  base.js; rename lt-header-username -> lt-header-user (matches CSS);
  add JSON_HEX_TAG to all inline json_encode calls (closes </script> XSS)
- base.css: add lt-kv-row/label/value aliases (display:contents pattern
  used in web_template v1.2 kv-grid); add lt-badge-sm variant
- Admin views: add missing .catch() on editField/editRecurring/loadUsers;
  add JSON_HEX_TAG to json_encode in TemplatesView/WorkflowDesignerView
- TicketView: add JSON_HEX_TAG to all ticket-data json_encode calls

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 12:43:24 -04:00
jared 51f6991f9d feat: nano-style footer bar, missing utility classes, CSS semantic vars
- layout_footer.php: add lt-footer with context-sensitive keyboard hint bar
  ([ ~ ] HOME | [ / ] SEARCH | [ + ] NEW | [ * ] CFG | [ ? ] HELP)
  Context adapts for dashboard, ticket, and admin pages
- layout_footer.php: wire show-keyboard-help and open-settings for all pages
- base.css: body { display:flex; flex-direction:column } + lt-main { flex:1 }
  so footer sticks to bottom of viewport on short pages
- base.css: add lt-flex-gap-xs/sm/md/lg and lt-flex-align-start/center/end
  (were used across all views but never defined — causing broken layouts)
- base.css: add --lt-danger/amber/cyan/success/text-primary CSS variables
  (referenced in ticket.css and dashboard.css fallbacks but never declared)
- base.css: add lt-text-danger/warning/success/info/primary utility classes
  (used in TicketView, DashboardView, admin views but not defined in base.css)
- DashboardView.php: remove ascii-banner.js (loaded but never called)
- TemplatesView.php: fix priority badge from lt-p* to lt-chip component

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 20:16:05 -04:00
jared 9bdeaf7731 fix: deep audit — wire TDS v1.2 components, fix kanban/tabs/bulk/avatar
- ticket.js: fix showTab() early return preventing attachments/deps from loading
- ticket.js: fix performStatusChange() overwriting lt-status-* classes
- dashboard.js: fix updateSelectionCount() using is-visible instead of style.display
- dashboard.js: fix populateKanbanCards() to use #kanban-col-* IDs (TDS v1.2)
- dashboard.js: fix setViewMode() removing references to old non-TDS elements
- dashboard.js: remove mobile-bottom-nav injection (no CSS existed for it)
- dashboard.css: add full lt-kanban-card component styles with priority accents
- dashboard.css: add mobile sidebar overlay, filter toggle, ticket preview popup CSS
- DashboardView.php: replace priority badges with lt-chip component
- TicketView.php: add lt-avatar with initials to comment author display
- ApiKeysView.php: enhance API usage section with lt-code-block component + curl example

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 19:58:14 -04:00
jared 79c2d2b513 feat: complete TDS v1.2 redesign across all views
Full application redesign using Terminal Design System v1.2 (lt-* class
system). Introduces shared layout_header/footer partials, upgrades
base.css/base.js to TDS v1.2, and rewrites all views (Dashboard, Ticket,
CreateTicket, and all 7 admin views) with lt-frame, lt-table, lt-modal,
lt-stats-grid, lt-kv-grid, and data-action event delegation patterns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 19:05:42 -04:00
jared ce95e555d5 CSS class migrations: admin views and boot overlay fade-out
- Replace style.display with .is-hidden classList in ApiKeysView, CustomFieldsView, RecurringTicketsView
- Convert boot overlay fade-out from style.opacity to .boot-overlay--fade-out CSS class
- Add .boot-overlay--fade-out rule to dashboard.css

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 21:20:55 -04:00
jared e35401d54e CSS class migrations for ticket page: tabs, visibility, markdown preview, uploads
- Switch tab show/hide from style.display to .tab-content.active CSS class
- Convert visibilityGroupsField, markdownPreview, uploadProgress to use .is-hidden class
- Replace comment text div style.display with classList.add/remove('is-hidden')
- Add .is-hidden utility class to ticket.css

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 21:13:55 -04:00
jared 913e294f9d CSS class migrations: stat-card cursor, view toggle, bulk actions visibility
- Replace stat-card cursor:pointer inline style with CSS rule
- Convert view toggle (table/card) to use .is-hidden CSS class
- Convert bulk-actions and export-dropdown to use .is-visible class
- Add .is-hidden/.is-visible utility rules to dashboard.css
- Remove duplicate lt.keys.initDefaults() call from dashboard.js
- Remove redundant setTimeout from view mode restore
- Add lt.keys.initDefaults() to dashboard.js (was missing entirely)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 21:08:28 -04:00
jared 31aa7d1b81 Fix JS SyntaxError breaking tabs, textarea scrolling, and XSS escaping
Bug fixes:
- ticket.js: Remove duplicate const textarea declaration inside showMentionSuggestions()
  (was redeclaring a parameter, causing SyntaxError that broke all tab switching)
- ticket.css: Add overflow:hidden + resize:none to disabled textarea so description
  shows full height without internal scrollbar (page scrolls instead)
- ticket.js: Trigger height recalculation when entering edit mode on description

XSS/escaping fixes:
- TicketView.php: htmlspecialchars() on description textarea content (closes </textarea> injection risk)
- TicketView.php: htmlspecialchars() on ticket status and workflow transition status strings
- DashboardView.php: htmlspecialchars() on $cat/$type in input value= attributes
- RecurringTicketsView.php: htmlspecialchars() on composed schedule string

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 20:34:55 -04:00
jared 7695c6134c Accessibility pass: ARIA roles, label associations, CSS class migrations
- Add role=dialog/aria-modal/aria-labelledby to all 12 modal overlays (JS + PHP)
- Add aria-label="Close" to all 14 modal close buttons
- Add full ARIA combobox pattern to @mention autocomplete (listbox, option, aria-selected, aria-expanded)
- Add for= attributes to admin filter form labels (AuditLog, UserActivity, ApiKeys)
- Remove dead closeOnAdvancedSearchBackdropClick() from advanced-search.js

CSS/JS style cleanup:
- Move .ascii-banner static styles from JS inline to CSS class; add .ascii-banner--glow
- Add .ascii-banner-cursor, .loading-overlay--hiding, .has-overlay, tr[data-clickable]
- Add .animate-fadein/.animate-fadeout/.comment--deleting to ticket.css
- Add .lt-toast--hiding to base.css; remove opacity/transition inline JS
- Remove redundant cursor:pointer JS (already in th{} CSS rule)
- Remove trailing space in lt-select class attributes

Bug fixes:
- base.js: boot overlay opacity inline style was overriding .fade-out class opacity via
  specificity (1000 vs 20), preventing the fade-out animation — removed
- ascii-banner.js: cursor used blink-caret (border-color only) instead of blink-cursor
  (opacity-based), so the █ cursor never actually blinked — fixed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 20:29:58 -04:00
jared 11f75fd823 Migrate all raw fetch() calls to lt.api, fix CSS fallback values
- Replace all 23 raw fetch() calls in dashboard.js and ticket.js with
  lt.api.get/post/delete — removes manual CSRF header injection,
  manual JSON parsing boilerplate, and response.ok checks throughout
- dashboard.js: 10 calls (inline save x2, template GET, 5x bulk ops,
  quick-status, quick-assign)
- ticket.js: 13 calls (main save, add/update/delete comment x3, reply,
  assign, metadata update, status change, deps GET/POST/DELETE,
  attachments GET, delete attachment)
- Remove stale csrf_token from deleteAttachment body (lt.api sends the
  X-CSRF-Token header automatically)
- Fix CSS variable fallbacks in ticket.css: replace
  var(--text-primary, #f7fafc) and var(--bg-secondary, #1a202c)
  with plain var(--text-primary) and var(--bg-secondary)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 11:27:46 -04:00
jared e179709fc3 Add lt.autoRefresh, fix showToast in admin, clean up inline styles
- Replace all 8 showToast() calls in ApiKeysView.php with lt.toast.*
  — all toast calls in the codebase now use lt.toast directly
- Add .duplicate-list, .duplicate-meta, .duplicate-hint CSS classes to
  dashboard.css; replace inline styles in duplicate detection JS with them
- Add dashboardAutoRefresh() using lt.autoRefresh — reloads page every
  5 minutes, skipping if a modal is open or user is typing in an input
- Add REFRESH button to dashboard header that triggers lt.autoRefresh.now()
  for immediate manual refresh with timer restart

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 11:16:18 -04:00
jared b03a9cfc8c Extract hardcoded rgba colors and inline styles to CSS classes
- Add .inline-error and .inline-warning utility classes to dashboard.css
  with correctly-matched terminal palette rgba values (replaces off-palette
  rgba(231,76,60,0.1) and rgba(241,196,15,0.1))
- Add .key-generated-alert class for the new API key display frame
- Add base .dependency-item, .dependency-group h4, .dependency-item a,
  .dependency-title, .btn-small overrides to ticket.css
- Remove all inline styles from the dependency list template in ticket.js
  — layout, colors, and sizing now come from CSS classes
- Update CreateTicketView.php and ApiKeysView.php to use the new classes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 11:08:52 -04:00
jared d44a530018 Extend lt.time.ago() to ticket view, replace showToast with lt.toast
- Add data-ts attributes to TicketView.php: ticket created/updated
  header, comment dates (inner span to preserve edited indicator),
  and all activity timeline dates
- Add initRelativeTimes() to ticket.js using lt.time.ago(); runs on
  DOMContentLoaded and every 60s to keep relative times current
- Attachment dates now use lt.time.ago() with full date in title attr
  and ts-cell span for periodic refresh
- Replace all 11 showToast() calls in ticket.js with lt.toast.* directly,
  removing reliance on the backwards-compat shim for these paths
- Add span.ts-cell and td.ts-cell CSS to both dashboard.css and ticket.css:
  dotted underline + cursor:help signals the title tooltip is available

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 11:03:34 -04:00
jared 1046537429 Move ASCII banner into boot sequence, fix remaining UI issues
- Remove collapsible ASCII banner from dashboard (was cluttering the UI)
- Show ASCII banner in the boot overlay on first session visit, above
  the boot messages, with a 400ms pause before messages begin
- Add scroll fade indicator (green-tinted gradient edges) to .table-wrapper
  so users can see when the table is horizontally scrollable
- Fix null guards for tab switcher in ticket.js (tabEl, activeBtn)
- Fix Reset → RESET uppercase in AuditLogView and UserActivityView

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 10:41:57 -04:00
jared d8220da1e0 Make dashboard table horizontally scrollable at smaller screen widths
- Set .table-wrapper to overflow-x: auto with touch scrolling support
- Add min-width: 900px to table to trigger scroll before columns collapse
- Set .ascii-frame-outer overflow-x: visible to avoid clipping conflict

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 10:35:45 -04:00
jared 90c5b3ff71 UI/UX polish: terminal design system alignment pass
Views:
- DashboardView.php: remove hardcoded [ ] from admin-badge button (CSS adds them)
- DashboardView.php: view toggle ≡/▦ → [ = ]/[ # ] (view-btn suppresses auto-brackets)
- DashboardView.php: clear-search ✗ → [ X ] (plain text, no auto-brackets on <a>)
- DashboardView.php: remove ↓ arrow emoji from export button text
- TicketView.php: tab labels → UPPERCASE (tab-btn CSS adds [ ] around text)
- TicketView.php: Edit Ticket/Clone/Add Comment/Add → title-case → UPPERCASE
- TicketView.php: reply button ↩ → [ << ] (comment-action-btn has no auto-brackets)

JavaScript:
- dashboard.js: modal/action button text all → UPPERCASE (CONFIRM/CANCEL/SAVE/ASSIGN/UPDATE/DELETE PERMANENTLY)
- dashboard.js: null guard in loadTemplate(), toggleSelectAll()
- ticket.js: null guards in addDependency(), handleFileUpload()

CSS:
- dashboard.css: z-index 1001/1002 magic numbers → var(--z-modal)/var(--z-popover)
- ticket.css: status-select hover/focus border rgba(white) → terminal palette

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 22:50:59 -04:00
jared 84bea80abd Fix PHP parse error and CSS/JS follow-on fixes
- DashboardView.php: fix PHP parse error on line 456/472/473/474 caused by
  escaped double-quotes {$row[\"key\"]} inside double-quoted echo strings;
  replaced with safe string concatenation . $row['key'] .
- ticket.css: fix status-select hover/focus border rgba(white) → terminal palette
- ticket.js: add null guards to addComment, togglePreview, updatePreview,
  toggleMarkdownMode, and addDependency element lookups

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 22:44:08 -04:00
jared 2f9af856dc Fix design system violations: replace off-brand colors with terminal palette
- dashboard.css: replace all hardcoded Tailwind hex colors (#2d3748, #1a202c,
  #e2e8f0, #4a5568, #007cba, #3b82f6 etc.) in dark-mode sections and component
  styles with terminal CSS variables (--bg-*, --text-*, --border-color,
  --terminal-green/amber)
- dashboard.css: fix card-priority colors white/black → var(--bg-primary)
- dashboard.css: fix card-assignee border-radius: 50% → 0 (no circles rule)
- dashboard.css: fix mobile bottom-sheet border-radius: 12px → 0
- dashboard.css: fix search-box focus border (#007cba → var(--terminal-green))
- dashboard.css: fix save-filter button blue (#3b82f6) → terminal green
- dashboard.css: fix search-results-info blue highlight → terminal green
- dashboard.css: fix btn-bulk/btn-secondary dark-mode bootstrap colors → terminal
- ticket.css: replace comprehensive dark-mode Tailwind hex block with CSS vars
- ticket.css: fix status-select white/black text → var(--bg-primary)
- ticket.css: fix status-select.status-resolved hardcoded #28a745 → var(--status-open)
- ticket.css: fix timeline dark-mode hardcoded colors → CSS vars
- ticket.css: fix .slider:before background white → var(--bg-primary)
- ticket.css: fix .btn-danger:hover color white → var(--bg-primary)
- ticket.css: fix visibility-groups-list label border-radius: 4px → 0
- ticket.css: add will-change: opacity to age-warning/age-critical animations
- views: bump CSS version strings to v=20260319c
- views/DashboardView.php: add aria-labels to card view quick action buttons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 22:37:19 -04:00
jared 27075a62ee Fix bracket buttons rendering below text + UI/security improvements
CSS fixes:
- Fix [ ] brackets appearing below button text by replacing display:inline-flex
  with display:inline-block + white-space:nowrap on .btn — removes cross-browser
  flex pseudo-element inconsistency as root cause
- Remove conflicting .btn::before ripple block (position:absolute was overriding
  bracket content positioning)
- Remove overflow:hidden from .btn which was clipping bracket content
- Fix body::after duplicate rule causing GPU layer blink (second position:fixed
  rule re-created compositor layer, overriding display:none suppression)
- Replace all transition:all with scoped property transitions in dashboard.css,
  ticket.css, base.css (prevents full CSS property evaluation on every hover)
- Convert pulse-warning/pulse-critical keyframes from box-shadow to opacity
  animation (GPU-composited, eliminates CPU repaints at 60fps)
- Fix mobile *::before/*::after blanket content:none rule — now targets only
  decorative frame glyphs, preserving button brackets and status indicators
- Remove --terminal-green-dim override that broke .lt-btn hover backgrounds

JS fixes:
- Fix all lt.lt.toast.* double-prefix instances in dashboard.js
- Add null guard before .appendChild() on bulkAssignUser select
- Replace all remaining emoji with terminal bracket notation (dashboard.js,
  ticket.js, markdown.js)
- Migrate all toast.*() shim calls to lt.toast.* across all JS files

View fixes:
- Remove hardcoded [ ] brackets from .btn buttons (CSS now adds them)
- Replace all emoji with terminal bracket notation in all views and admin views
- Add missing CSP nonces to AuditLogView.php and UserActivityView.php script tags
- Bump CSS version strings to ?v=20260319b for cache busting

Security fixes:
- update_ticket.php: add authorization check (non-admins can only edit their own
  or assigned tickets)
- add_comment.php: validate and cast ticket_id to integer with 400 response
- clone_ticket.php: fix unconditional session_start(), add ticket ID validation,
  add internal ticket access check
- bulk_operation.php: add HTTP 401/403 status codes on auth failures
- upload_attachment.php: fix missing $conn arg in AttachmentModel constructor
- assign_ticket.php: add ticket existence check and permission verification

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 22:20:43 -04:00
jared dd8833ee2f Restore visual effects using GPU-safe techniques (no repaint triggers)
Rules: transform/opacity = GPU composited (fine). box-shadow/text-shadow on
hover = CPU repaint (removed). Static box-shadow/text-shadow = painted once (fine).

- Buttons (.btn, .btn-base, button, .btn-primary): add will-change:transform
  for pre-promotion, add transform:translateY(-1px) on hover (GPU, no repaint),
  scope transition to include transform, remove box-shadow/text-shadow from hover
- Stat cards: add will-change:transform, add transform:translateY(-2px) on hover
- Priority badges: replace filter:blur(6px) ::after pseudo-element (permanent GPU
  layer per badge, ~20 on screen at once) with static box-shadow:0 0 6px currentColor
  on the badge itself — painted once, never changes, zero compositor overhead
- Links: replace opacity-transition ::after underline (lazy GPU layer creation on
  hover) with text-decoration:underline on hover (pure CPU paint, no GPU layer)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 19:28:28 -04:00
jared ab3e77a9ba Fix blink root cause: eliminate position:fixed GPU compositing layers
Chrome promotes ALL position:fixed elements to GPU compositing layers for scroll
performance, regardless of whether they have animations. The body::before scanline
overlay (position:fixed, z-index:9999, full-viewport) and body::after watermark
(position:fixed) were both on GPU layers. Every CPU repaint from any hover state
change required a compositor re-blend pass → one-frame blink at compositor sync.

Fixes:
- Move scanlines from body::before (position:fixed) into body { background-image }
  — same visual, no separate element, no GPU layer promotion
- Set body::before { display:none } and body::after { display:none } in both
  dashboard.css and base.css
- Remove animation:matrix-rain from .stat-card:hover::before — background-position
  animation is not GPU-composited, caused CPU repaints every frame while hovered
  plus GPU texture uploads when animation started/stopped on cursor enter/exit
- Scope a { transition: all } → transition: color in base.css

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:23:30 -04:00
jared 68ff89b48c Fix persistent blink: scanline animation still active via base.css cascade
Root cause: removing 'animation' from dashboard.css body::before did NOT disable
the scanline — it just stopped overriding base.css which still had
'animation: scanline 8s linear infinite'. CSS cascade means the base.css value
remained active. Fixed by setting 'animation: none' explicitly in dashboard.css.

Also fix base.css (used by all pages including ticket page):
- Set body::before animation: none (removes GPU compositing layer from scanline)
- Change corner-pulse/subtle-pulse/pulse-glow/pulse-red keyframes from text-shadow
  and box-shadow animations to opacity (GPU composited, zero CPU repaint overhead)
- Change exec-running-pulse from box-shadow to opacity
- Remove box-shadow from .lt-table tr:hover, .lt-card:hover, .lt-stat-card:hover
- Remove text-shadow/box-shadow/transform from .lt-btn:hover and variants
- Remove text-shadow from a:hover

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:12:44 -04:00
jared 328c103460 Fix ascii-frame-outer blink: eliminate all repaint-causing hover effects
- Change pulse-glow keyframes from text-shadow animation to opacity (GPU composited,
  eliminates 60fps CPU repaint that was the likely root cause of the persistent blink)
- Remove box-shadow from .quick-action-btn:hover; scope transition: all → background/color
- Remove box-shadow + background gradient + transform:translateY from .stat-card:hover;
  scope transition: all → border-color only
- Remove .stat-card::after transition and hover background change
- Remove duplicate .stat-card:hover transform:translateY block
- Remove box-shadow from .clear-search-btn:hover; scope transition: all → background/color
- Remove text-shadow from th transition (th:hover never changes text-shadow)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:06:39 -04:00