Phase 0: Full frontend modernization — Laravel 12, Vite, Blade, Tiptap, design system, accessibility#3259
Open
gloriafolaron wants to merge 428 commits intomasterfrom
Open
Phase 0: Full frontend modernization — Laravel 12, Vite, Blade, Tiptap, design system, accessibility#3259gloriafolaron wants to merge 428 commits intomasterfrom
gloriafolaron wants to merge 428 commits intomasterfrom
Conversation
Contributor
Author
Update: Timesheets, Menu Icons, and UI Polish@marcelfolaron — new batch of changes pushed, summary below. New in this push (19 commits)Timesheets
UI Polish
Menu
|
…aisyUI to Bootstrap Replace tw:/DaisyUI classes with Bootstrap equivalents in project hub, project cards, user management, client details, and company settings templates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…endar from DaisyUI to Bootstrap Replace tw:/DaisyUI classes with Bootstrap equivalents in dashboard widgets, welcome panel, head menu, project selector, stopwatch, timesheets, and calendar export templates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…es from DaisyUI to Bootstrap Replace tw:/DaisyUI classes with Bootstrap equivalents in canvas elements, goal dashboard, idea dialogs, strategy boards, wiki dialogs, comments, and file browser templates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…DaisyUI to Bootstrap Replace tw:/DaisyUI classes with Bootstrap equivalents in login, user invites, API keys, integrations, onboarding help steps, and plugin marketplace templates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tion Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…s files nyroModal library was removed but CSS selectors targeting .nyroModalCont remained in tiptap-editor.css and theme dark.css files. Also deletes unused Less files (main.less, app.less) that referenced removed libraries and were not part of the Vite build pipeline. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… quickadd.less Remove orphaned .nyroModalCloseButton mobile rule (native dialog handles its own close button). Delete quickadd.less — duplicate of quickadd.css with minor differences, not part of the build pipeline. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Regenerated CSS bundles with nyroModal selectors removed from tiptap-editor, mobile, and theme dark.css source files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Delete loading.css (120KB), loading-btn.css (4KB), switchery.min.css, and jquery.ui.css — all confirmed unused. Remove ld-ext-right spinner classes from 3 blade templates (save buttons on user profile, project details, company settings). Saves ~125KB of dead CSS. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…omains Remove superseded .tpl.php templates from all 15 canvas variant domains (Cpcanvas, Dbmcanvas, Eacanvas, Emcanvas, Insightscanvas, Lbmcanvas, Leancanvas, Minempathycanvas, Obmcanvas, Retroscanvas, Riskscanvas, Sbcanvas, Smcanvas, Sqcanvas, Swotcanvas). Each domain's 5 files (showCanvas, canvasDialog, canvasComment, delCanvas, delCanvasItem) have verified Blade equivalents that are already active. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… 11 other domains Remove superseded .tpl.php templates: Ideas (5), Wiki (6), Connector (8), Clients (5), Errors (4), Files (2), Comments (1), Api (3), TwoFA (2), Install (2), CsvImport (1), Reports (1), Strategy (1). All have verified Blade equivalents that are already active. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add success, warning type variants to both button components. Consolidate danger/error aliases. Add ghost, transparent mappings to button-dropdown for parity with the main button component. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rewrite .row and .col-* definitions as clean flexbox without vendor prefixes. Fix column widths to correct 12-column percentages (8.333% instead of the old incorrect 8.555%). Remove unused push-*, pull-*, and offset-* grid utilities (zero template references). Add proper responsive breakpoints for col-sm-*, col-lg-* alongside existing col-md-*. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
View/sort/search filters moved from above the page title to a properly aligned actions bar on the same line as the title. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The fourth stat card label ('Goals you are contributing to') wraps to
multiple lines while shorter labels fit on one line, causing visual
inconsistency. Adding whitespace-nowrap with text-ellipsis overflow
to the big-number-box component ensures all stat card labels render
at a consistent single-line height.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ing is accessible Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Submenus stayed collapsed when navigating to pages within them (e.g., Ideas under THINK, Company Settings under Administration). This fixes two issues: (1) moves the visual state assignment inside the else block to prevent an undefined $submenuState from overwriting the 'always' case, and (2) adds logic to check if any submenu child matches the current module+action and forces the submenu open if so. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…i translations
The global __() helper race condition: Laravel's helpers.php defines __()
before Leantime's app/helpers.php loads (Composer autoload order), so
Leantime's version never registers. Laravel's __() calls
app('translator')->get(), which should resolve to Leantime's Language
class. The LanguageServiceProvider used alias() for this binding, but
a direct singleton is more robust against deferred provider overrides.
Also share $tpl via View::share() in Template::display() so the
template instance is available in all Blade views (layouts, components).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…font All 7 chip-select templates referenced 'material-symbols-rounded' but the CSS bundle only includes the @font-face for 'Material Symbols Outlined', causing icon names to render as literal text. Changed to 'material-symbols-outlined' to match icon.blade.php and the loaded font. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…timesheetsController These functions are called from jQuery.ready() so the DOM is already loaded. The nested DOMContentLoaded listeners never fire, preventing DataTable initialization on the users and timesheets pages. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… initializes
initMilestoneTable() selects via jQuery('.ticketTable') but the milestone
templates only had 'dataTable' from the component — missing 'ticketTable'
caused silent init failure, losing sort indicators, Export/Columns buttons,
and hidden column defaults.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…dule load The inline script in showAllMilestonesOverview.blade.php called leantime.ticketsController methods outside jQuery(document).ready(), but entry-app.js loads as a deferred ES module. This caused the controller to be undefined when the inline script executed. Moving all controller calls inside jQuery(document).ready() ensures the module has registered before the calls run. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…mission Conditionally render hx-post/hx-trigger/hx-swap/hx-vals only when ticketId is present, preventing 400 errors on new ticket forms. Add hx-include="this" so HTMX only sends the chip's own value instead of the entire parent form, fixing 'Unknown column saveTicket' SQL errors on existing ticket pages. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The get() method only returned a Response for the 'game=snake' query parameter. Without it, get() returned null, causing the Frontcontroller to access an uninitialized $response property and trigger a fatal error. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…les empty message misplacement The table.blade.php component already wraps the default slot in <tbody>, so the explicit <tbody></tbody> in showAll.blade.php created a duplicate. DataTables inserted its "Nothing found!" message into the first (empty) tbody while data rows rendered in the second, making the empty message always visible. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…full page injection When a ticket cannot be found, the error path was calling display() which renders a full HTML page. This response gets injected into #ticketContent via jQuery.get, causing duplicate page chrome. Changed to displayPartial() in both GET and POST handlers for consistency with the success path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ink being unclickable Add pointer-events:none to .timerContainer so it doesn't intercept clicks on chip-selects inside it, with pointer-events:auto on direct children. Change kanbanCardContent and h4 overflow from hidden to visible and set h4 a to display:inline-block so the title link renders with proper dimensions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…t overflow into title Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…button visibility leak The Blade button component renders fileupload-exists buttons as <a class="btn btn-primary fileupload-exists"> whose forms.css rule `body a.btn.btn-primary` (specificity 0,2,2) overrides the bootstrap-fileupload hide rule `.fileupload-new .fileupload-exists` (specificity 0,2,0), causing Save/Remove buttons to be visible in the default no-file-selected state. Bump specificity to (0,3,0) with `.fileupload.fileupload-new .fileupload-exists`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…element in table view Pass size=sm and noText=true to the milestone progress HTMX endpoint so the table column renders a slim progress bar instead of the full card-style progress with text labels that resembled a toggle switch. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…MX partial templates The HTMX partial had hardcoded English strings for 'My Favorites' and 'All Assigned Projects' that lost emoji formatting and i18n after refresh. Added text.my_favorites key and replaced hardcoded strings with __() calls in both templates for consistency. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hardcoded strings in showProject.blade.php (archive status message), showMy.blade.php (Save button, timezone tooltip texts) with proper __() calls and add corresponding keys to en-US.ini. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…line The title column's data-search was incorrectly set to the status label name (copy-paste from the status column), causing DataTables search to match status text instead of the actual ticket title. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…L IDs When the same ticket appears in multiple contexts (kanban board + detail panel), the timerContainer elements get duplicate IDs. Append uniqid() to each timerContainer ID across all templates to ensure uniqueness. HTMX self-refresh uses hx-target="this" so is unaffected by the ID change. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Notes plugin exists in the OSS codebase but its menu entry was only injected via plugin event listener. Move the menu item into the core personal menu structure at index 10 (between Project Hub and Timesheets) and add proper i18n keys. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…iles
The text.no_blueprints_yet translation value contained a trailing <br/>
in 42 language files (all except en-US.ini). Since the Blade template
renders this with {{ }} (escaped output), the <br/> appeared as visible
literal text instead of a line break. The template already has proper
<br /> tags for spacing, so the HTML in translations was unnecessary.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…icket The NewTicket controller always used displayPartial() which rendered the modal template without the application shell. Now it checks for HTMX/AJAX requests and only uses the partial for those; direct navigation gets the full-page newTicket.blade.php with proper layout. Also removed the inline visibility:hidden style from the modal template tabs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…h 13 columns The showMyList page was an inline-editing form with 7 columns and no pagination. The reference design is a read-only DataTables reporting view with 13 columns (ID, Date, Hours, Plan Hours, Difference, Ticket, Project, Employee, Type, Description, Invoiced, MGR Approval, Paid), Export/Columns buttons, and pagination. - Rewrote template to use DataTables with all 13 columns, matching showAll pattern - Added Export (CSV) and Columns visibility buttons - Added pagination via DataTables (100 entries per page) - Removed inline editing form, new entry row, and Save button - Simplified controller by removing save logic (no longer needed) - Removed unused ProjectService and TicketService dependencies - Removed "Overview" subtitle from page header - Added initMyTimesheetsTable JS function for the new table Resolves: fix-039, timesheets-showMyList-001, timesheets-showMyList-002, timesheets-showMyList-007, timesheets-showMyList-008, timesheets-showMyList-009 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…log input width When dialog fragments using the blank layout are accessed directly (not via AJAX/HTMX), they now get a proper HTML wrapper with the app's CSS loaded. AJAX requests continue to receive bare fragments as before. Also adds an inline width:100% fallback on the boardDialog text input for when Tailwind classes aren't available. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…plate When /tickets/showTicket is accessed without an ID, the 400 error page displayed raw i18n keys (e.g. 'headlines.bad_request') instead of readable text. This adds hardcoded English fallbacks so the template renders correctly even when the Language service hasn't fully loaded the ini translations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The '// All To-Dos' subtitle dropdown in the timelineHeader submodule was conditionally hidden when no sprints existed, unlike ticketHeader which always shows the dropdown. Removed the @if guard so the dropdown is always visible on Roadmap and Calendar pages, matching the reference. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…users page Add left padding to gantt-wrapper so the first month label is fully visible, and set flex-initial on users maincontentinner so it sizes to content instead of stretching to fill the viewport. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
This is a ground-up modernization of Leantime's entire frontend stack. It touches 1,086 files across 310 commits — every layer from the build system to the template engine to the CSS architecture to the JavaScript runtime. The goal is to replace a decade of accumulated legacy tooling with a modern, maintainable foundation that can support Leantime's next generation of features.
This is not a cosmetic refresh. It is an infrastructure overhaul that changes how templates are authored, how styles are defined, how JavaScript is loaded, how modals work, how forms are built, and how the design system is governed — while preserving every existing feature and user-facing behavior.
By the numbers:
.tpl.php,.sub.php,.inc.php)1. Build System & Framework Upgrades
Laravel 12
Upgraded from Laravel 11 to Laravel 12. Updated framework dependencies, service providers, and kernel configuration to match the new structure.
Laravel Mix → Vite
Replaced the Webpack-based Laravel Mix build with Vite. This is not just a config swap — the entire asset pipeline was restructured:
require()@rollup/plugin-commonjsto handle legacy CJS libraries (jstree, DataTables, FullCalendar) that cannot be convertedimport $ from 'jquery'works throughout the codebase without bundling jQuery multiple timesTailwind CSS 3.4 → 4.x
Upgraded to Tailwind v4's CSS-native configuration model:
tailwind.config.jswith CSS-based config inresources/css/app.csstw-(hyphen) totw:(colon) across every template@tailwind componentsand@tailwind utilitiesare active (base layer disabled to avoid conflicts with existing styles)HTMX 1.x → 2.x
Upgraded HTMX to version 2 and added the Idiomorph morphing extension for smoother DOM diffing during partial swaps. Added the
head-supportandpreloadextensions.2. Template Migration — Legacy PHP to Blade
Complete conversion
Every legacy
.tpl.php,.sub.php, and.inc.phptemplate has been converted to Laravel Blade (.blade.php). This was done domain by domain:After conversion, 142 legacy template files were deleted and the legacy template extension registration was removed from the view system. The application no longer loads or renders
.tpl.phpfiles.Bootstrap grid class removal
After the Blade conversion, every template was scrubbed to remove Bootstrap-specific grid classes (
col-md-*,row,offset-*,pull-right,clearfix) across all domains including Help, Canvas, Goalcanvas, Strategy, shared Views, and plugin templates. These were replaced with flexbox utilities or lightweight CSS shims where layout was still needed.3. Shared Blade Component Library
A reusable component library was built in
app/Views/Templates/components/to standardize all UI elements across the application. Every hardcoded HTML form element and interactive pattern was systematically converted to use these components:Form components
forms/input<input type="text|hidden|number|email|password|time">:bareprop for inline contextsforms/select<select>:bareprop, grouped optionsforms/textarea<textarea>forms/checkbox<input type="checkbox">forms/radio<input type="radio">forms/date<input class="dates">forms/file<input type="file">:barepropUI components
buttonpageheadertabsloader/loadingTextelements/badgeelements/cardprogressavatardateInlineThe
bareprop patternAll form components support
:bare="true"which outputs just the raw HTML element with no wrapper markup and no framework classes. This is essential for:DaisyUI evaluation
DaisyUI 5 was installed and all templates were converted to use its component classes. After thorough testing, DaisyUI was reverted — the class naming conflicts with Leantime's existing design token system created more problems than it solved. The component conversions remain (Blade components are still used), but they output Bootstrap-compatible class names styled by our own CSS. The DaisyUI package remains installed for future consideration but is not actively used for production styling.
4. CSS Architecture & Design System
Design token system
The entire CSS codebase was systematically swept to replace hardcoded values with CSS custom properties. This was done in 17 numbered sweeps covering every property type:
#004666,#1b75bb, etc. →var(--accent1),var(--accent2)var(--primary-font-color),var(--secondary-background), etc.focus-visibleoutlines added to all custom interactive elementsaria-labels,aria-hidden, and form labels added throughoutrole=status,sr-onlytext,aria-live=politeon all HTMX indicators/targetsborder-radius→var(--box-radius)scaleSemantic alias layer
Added
accent3andaccent4tokens, plus semantic aliases that map purpose to value (e.g.,--feedback-success,--feedback-warning). This lets themes change meaning without hunting through hundreds of property declarations.Glass morphism
Added a tiered glass effect system with CSS custom properties:
--glass-blur,--glass-background,--glass-borderfor standard glass--glass-blur-subtletier for lighter effects on widgets and selectablesTypography
Added Hanken Grotesk as the new default font family, with fallbacks to Roboto and system fonts. All font-size, font-weight, and line-height values across the CSS codebase were normalized to use the design token scale.
Design system naming (v3)
Implemented a comprehensive naming convention pass (Phases 0–5) to make all CSS custom properties consistent and predictable. This renamed tokens across the entire system to follow a
--{category}-{property}-{variant}pattern.5. JavaScript Modernization
jQuery elimination
Approximately 600 jQuery calls across all domain controllers were converted to vanilla JavaScript. This includes:
$(document).ready()→DOMContentLoadedlisteners$.ajax()→fetch()with proper credentials and headers$('.selector').on('click')→addEventListenerwith event delegation$.each(),$.extend(),$.trim()→ native equivalents$(el).show/hide/toggle()→classListandstyle.displaymanipulation$(el).val(),.text(),.html()→.value,.textContent,.innerHTMLjQuery UI removal
tabsController(readsdata-tab-targetattributes)nestedSortablerewritten to use SortableJS internallyOther library replacements
<dialog>+modalManager.jsDomain JS lazy-loading
Instead of loading all 46 domain JS files on every page (~110 KB), the build now uses
import.meta.globto split them into individual chunks. Only the active module's scripts are loaded:$modulevariable determines which domain JS to fetchhx-boostdomain JS reloader automatically loads the correct scripts when SPA-style HTMX navigation changes the active modulePerformance impact
Eliminated approximately 4 MB from the initial page load:
6. Rich Text Editor — TinyMCE → Tiptap
The monolithic TinyMCE editor (3.6 MB, version 5.10.9 with ~20 custom plugins) was replaced with a modern Tiptap-based editor built on ProseMirror:
Features
/for a command palette (headings, lists, code blocks, tables, etc.)@to search and mention usersThree editor variants
Integration
7. Modal System — nyroModal → Native Dialog
Replaced the jQuery-based nyroModal plugin with a modern modal system built on the browser's native
<dialog>element:modalManager.js<dialog>overlaysfetch(), injects into the dialog, and manages lifecyclebackdrop-filter: blur(12px)with 50% opacity overlayhx-boostSPA navigationFormDatawith submit button values#/tickets/showTicket/123) for deep-linkable modalsMigration scope
$.nmManual()and$.nyroModal()calls converted<div class="modal fade">patterns converteddata-dismiss="modal"attributes removedjQuery(...).modal('show'|'hide')calls eliminated8. HTMX & SPA Navigation
hx-boost navigation
Added
hx-boost="true"to the main content area, enabling SPA-like navigation where clicking links swaps just the page content instead of doing a full page reload. This includes:hx-targetandhx-selectattributes to prevent the target inheritance bug (where child HTMX elements inherit the parent's swap target and accidentally replace the entire page)HTMX fixes
hx-selectinheritance — added explicit targets to all HTMX elements inside.primaryContentto prevent the.rightpanelancestor's swap target from being inheritedaria-live="polite"to all HTMX swap target containers for screen reader announcements<script>tags after DOMParser innerHTML swaps (HTMX strips script tags by default)9. Accessibility Improvements
Systematic accessibility improvements applied across the entire application:
focus-visibleoutlines added to all custom interactive elements (cards, clickable divs, icon buttons)aria-labelsadded to all icon-only buttons and links,aria-hidden="true"on decorative iconsfor/idattributesrole="status"andsr-onlydescriptive textaria-live="polite"for dynamic content announcementstabindex="0"andEnter/Spacehandlersmaximum-scale=1from viewport meta tag to allow user zoom (WCAG 1.4.4)aria-labelsto color-only priority border ticket cards10. Mobile & Responsive Improvements
100vhreplaced with100dvh(dynamic viewport height) for correct mobile browser behaviormax-widthvaluestransition: allwith specific property transitions to prevent layout thrashing on mobile11. Page-Specific Fixes & Polish
Timesheets
Calendar
Wiki
Notes
Dashboard & Widgets
Project Settings
Kanban
Admin & Settings
Plugins / Apps Page
Other fixes
12. Submodule Updates
The
app/Plugins/submodule was updated throughout to keep plugins compatible with the modernization changes:tw-prefix totw:for Tailwind v4Known Remaining Work
Bootstrap patterns still present
data-toggle="dropdown"dropdownPill+inlineSelectshared components cascades to ~30 consumers.nav-tabs/.nav-pills.alert.alert-*.label.label-*.btn-group.form-groupOther
secretInputinline date pickers were intentionally skipped (specialized transparent styling)Test Plan
Core Navigation
Tickets & Kanban
Projects
Timesheets
Calendar
Wiki
Notes
Canvas / Goals
Forms & Components
:bareprop produces clean, unwrapped outputEditor
Modals
Admin & Settings
Visual & Responsive
var()references)Accessibility
cc @marcelfolaron
🤖 Generated with Claude Code