fix: Mobile hamburger menu and iPad sidebar visibility

Mobile fixes:
- Add click toggle handler for hamburger menu (was hover-only)
- Menu now opens/closes on tap and closes when clicking outside
- Keep hover support for desktop

iPad fixes:
- Sidebar content now visible on touch devices (901-1280px)
- Added (hover: hover) media query to prevent hide-on-hover on tablets

Security improvements:
- Replace exec.CommandContext with go-git library for git operations
- Add path traversal and command injection prevention
- Fix race condition in template hot reload
- Add environment-based cookie Secure flag

Code quality:
- Add constants.go for magic numbers
- Remove unused code (ParsePreferenceToggleRequest, DomainError)
- Add FOUC prevention with inline critical CSS
- Add Makefile dev/run/clean targets
- Fix README git clone URL
- Add doc/DECISIONS.md for architectural decisions

Tests:
- Add hamburger menu click toggle tests
- Add iPad sidebar visibility tests
- Update security tests for go-git implementation
- Add cookie Secure flag tests
This commit is contained in:
juanatsap
2025-11-30 09:29:35 +00:00
parent 60c1b5ac2b
commit eb92f64e93
18 changed files with 874 additions and 183 deletions
+24 -2
View File
@@ -34,7 +34,11 @@ func NewManager(cfg *config.TemplateConfig) (*Manager, error) {
func (m *Manager) loadTemplates() error {
m.mu.Lock()
defer m.mu.Unlock()
return m.loadTemplatesLocked()
}
// loadTemplatesLocked parses templates without acquiring lock (caller must hold lock)
func (m *Manager) loadTemplatesLocked() error {
// Create template with custom functions
funcMap := template.FuncMap{
"iterate": func(count int) []int {
@@ -109,15 +113,33 @@ func (m *Manager) Reload() error {
}
// Render executes a template with the given data
// Note: This method is thread-safe. Hot reload acquires full lock to prevent race conditions.
func (m *Manager) Render(name string) (*template.Template, error) {
// Hot reload in development mode
// Use full lock to prevent race condition between reload and lookup
if m.config.HotReload {
if err := m.Reload(); err != nil {
m.mu.Lock()
if err := m.loadTemplatesLocked(); err != nil {
m.mu.Unlock()
log.Printf("Warning: template reload failed: %v", err)
// Continue with cached templates
// Fall back to read lock for cached templates
m.mu.RLock()
defer m.mu.RUnlock()
tmpl := m.templates.Lookup(name)
if tmpl == nil {
return nil, fmt.Errorf("template %q not found", name)
}
return tmpl, nil
}
tmpl := m.templates.Lookup(name)
m.mu.Unlock()
if tmpl == nil {
return nil, fmt.Errorf("template %q not found", name)
}
return tmpl, nil
}
// Production mode: just read
m.mu.RLock()
defer m.mu.RUnlock()