diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 9e241a1..ebb126d 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -1,6 +1,41 @@
TASKMATE - CHANGELOG
====================
+================================================================================
+11.01.2025 - FEATURE: Coding-Modul - Freie Pfad-Eingabe
+================================================================================
+
+## ÄNDERUNG
+Ordnerpfad für Coding-Anwendungen kann jetzt frei definiert werden
+
+## IMPLEMENTIERUNG
+✅ Neues Eingabefeld für Ordnerpfad im Modal
+✅ Pfad wird nicht mehr automatisch aus Namen generiert
+✅ Volle Flexibilität für beliebige Pfadangaben
+✅ Validierung auf leeren Pfad
+
+================================================================================
+10.01.2026 - FEATURE: Optionales zweispaltiges Layout für Aufgabenansicht
+================================================================================
+
+## NEUE FUNKTION
+Möglichkeit, Aufgaben in der Board-Ansicht mehrspaltig anzuzeigen
+
+## IMPLEMENTIERUNG
+✅ Toggle-Button in der Filter-Leiste hinzugefügt
+✅ Dynamisches Grid-Layout: Spalten erweitern sich nur bei Bedarf
+✅ Automatische Erkennung wenn Scrollen nötig wäre
+✅ Layout-Präferenz wird in localStorage gespeichert
+✅ 2 Spalten wenn Inhalt > 120% der Höhe (ab 1400px Breite)
+✅ 3 Spalten wenn Inhalt > 250% der Höhe (ab 1800px Breite)
+✅ Spalten-Breite passt sich dynamisch an
+✅ Cache-Version erhöht auf 303
+
+## BEDIENUNG
+- Button mit Raster-Icon in der Filter-Leiste schaltet Layout um
+- Layout bleibt über Browser-Sessions erhalten
+- Aufgaben werden bei aktivem Layout nebeneinander statt untereinander angezeigt
+
================================================================================
10.01.2026 - BUGFIX: Download von Dateianhängen funktioniert wieder
================================================================================
diff --git a/data/taskmate.db b/data/taskmate.db
index bb1c53d..4fe697e 100644
Binary files a/data/taskmate.db and b/data/taskmate.db differ
diff --git a/frontend/css/board.css b/frontend/css/board.css
index 5720a5d..0c6c714 100644
--- a/frontend/css/board.css
+++ b/frontend/css/board.css
@@ -648,6 +648,7 @@
min-height: 0;
overflow-x: auto;
padding: var(--spacing-4);
+ align-items: flex-start;
}
/* Column */
@@ -662,6 +663,7 @@
border-radius: var(--radius-xl);
box-shadow: var(--shadow-sm);
flex-shrink: 0;
+ transition: width var(--transition-default), min-width var(--transition-default);
}
.column-header {
@@ -1259,3 +1261,90 @@
font-size: var(--text-2xs);
flex-shrink: 0;
}
+
+/* ========================================
+ MULTI-COLUMN LAYOUT
+ ======================================== */
+
+/* Standard Layout (einspalltig) */
+.board .column-body {
+ flex: 1;
+ padding: var(--spacing-2);
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-2);
+}
+
+/* Base Multi-Column Layout - aktiviert das Feature, aber zeigt noch einspaltig */
+.board.multi-column-layout .column-body {
+ /* Bleibt erstmal bei flex layout bis Inhalt zu lang wird */
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-2);
+}
+
+/* Dynamisch aktivierte 2-spaltige Ansicht (wenn Scrollen nötig wäre) */
+.board.multi-column-layout .column-body.dynamic-2-columns {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ grid-auto-flow: row;
+ gap: var(--spacing-2);
+ align-content: start;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+/* Dynamisch aktivierte 3-spaltige Ansicht (wenn viel Inhalt) */
+.board.multi-column-layout .column-body.dynamic-3-columns {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ grid-auto-flow: row;
+ gap: var(--spacing-2);
+ align-content: start;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+/* Spalten-Breite wenn erweitert */
+.board.multi-column-layout .column {
+ transition: width var(--transition-default), min-width var(--transition-default);
+}
+
+.board.multi-column-layout .column.expanded-2x {
+ width: auto;
+ min-width: 560px;
+ max-width: 640px;
+}
+
+.board.multi-column-layout .column.expanded-3x {
+ width: auto;
+ min-width: 840px;
+ max-width: 960px;
+}
+
+/* Task Cards im Multi-Column Layout */
+.board.multi-column-layout .task-card {
+ width: 100%;
+ box-sizing: border-box;
+}
+
+/* Hover-Effekt für Layout-Toggle Button */
+#btn-toggle-layout {
+ transition: all var(--transition-fast);
+}
+
+#btn-toggle-layout:hover {
+ transform: rotate(90deg);
+}
+
+/* Active state indicator - korrigiert für richtige Selektion */
+.view-board.active .board.multi-column-layout ~ * {
+ /* Dummy rule to ensure the layout class is applied */
+}
+
+/* Layout toggle button active state */
+#btn-toggle-layout.active {
+ color: var(--primary);
+ background: var(--primary-light);
+}
diff --git a/frontend/index.html b/frontend/index.html
index 6f8c463..af53c46 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -357,6 +357,14 @@
+
@@ -1696,7 +1704,11 @@
- Ordner: /home/claude-dev/...
+
+
+
+
+ Vollständiger Pfad zum Projekt-Ordner
diff --git a/frontend/js/board.js b/frontend/js/board.js
index 19ac77b..8f93f0e 100644
--- a/frontend/js/board.js
+++ b/frontend/js/board.js
@@ -25,6 +25,9 @@ class BoardManager {
// Week strip calendar
this.weekStripDate = this.getMonday(new Date());
this.tooltip = null;
+
+ // Layout preferences
+ this.multiColumnLayout = this.loadLayoutPreference();
this.init();
}
@@ -52,6 +55,21 @@ class BoardManager {
this.loadStats();
this.renderWeekStrip();
});
+
+ // Apply initial layout preference
+ setTimeout(() => {
+ this.applyLayoutClass();
+ if (this.multiColumnLayout) {
+ this.checkAndApplyDynamicLayout();
+ }
+ }, 100);
+
+ // Re-check layout on window resize
+ window.addEventListener('resize', debounce(() => {
+ if (this.multiColumnLayout) {
+ this.checkAndApplyDynamicLayout();
+ }
+ }, 250));
}
// =====================
@@ -144,6 +162,13 @@ class BoardManager {
// Keyboard navigation
document.addEventListener('keydown', (e) => this.handleKeyboard(e));
+
+ // Layout toggle button - use delegated event handling
+ document.addEventListener('click', (e) => {
+ if (e.target.closest('#btn-toggle-layout')) {
+ this.toggleLayout();
+ }
+ });
}
// =====================
@@ -155,6 +180,9 @@ class BoardManager {
const columns = store.get('columns');
clearElement(this.boardElement);
+
+ // Apply layout class
+ this.applyLayoutClass();
columns.forEach(column => {
const columnElement = this.createColumnElement(column);
@@ -170,6 +198,9 @@ class BoardManager {
'Statuskarte hinzufügen'
]);
this.boardElement.appendChild(addColumnBtn);
+
+ // Check dynamic layout after render
+ setTimeout(() => this.checkAndApplyDynamicLayout(), 100);
}
createColumnElement(column) {
@@ -475,6 +506,9 @@ class BoardManager {
countElement.textContent = filteredTasks.length.toString();
}
});
+
+ // Check if dynamic layout adjustment is needed
+ setTimeout(() => this.checkAndApplyDynamicLayout(), 100);
}
// =====================
@@ -1461,6 +1495,103 @@ class BoardManager {
div.textContent = str;
return div.innerHTML;
}
+
+ // =====================
+ // LAYOUT PREFERENCES
+ // =====================
+
+ loadLayoutPreference() {
+ const stored = localStorage.getItem('taskmate:boardLayout');
+ return stored === 'multiColumn';
+ }
+
+ saveLayoutPreference(multiColumn) {
+ localStorage.setItem('taskmate:boardLayout', multiColumn ? 'multiColumn' : 'single');
+ }
+
+ toggleLayout() {
+ this.multiColumnLayout = !this.multiColumnLayout;
+ this.saveLayoutPreference(this.multiColumnLayout);
+ this.applyLayoutClass();
+ this.checkAndApplyDynamicLayout();
+
+ this.showSuccess(this.multiColumnLayout
+ ? 'Mehrspalten-Layout aktiviert'
+ : 'Einspalten-Layout aktiviert'
+ );
+ }
+
+ applyLayoutClass() {
+ const toggleBtn = $('#btn-toggle-layout');
+
+ if (this.multiColumnLayout) {
+ this.boardElement?.classList.add('multi-column-layout');
+ toggleBtn?.classList.add('active');
+ } else {
+ this.boardElement?.classList.remove('multi-column-layout');
+ toggleBtn?.classList.remove('active');
+
+ // Remove all dynamic classes when disabled
+ const columns = this.boardElement?.querySelectorAll('.column');
+ columns?.forEach(column => {
+ const columnBody = column.querySelector('.column-body');
+ columnBody?.classList.remove('dynamic-2-columns', 'dynamic-3-columns');
+ column.classList.remove('expanded-2x', 'expanded-3x');
+ });
+ }
+ }
+
+ checkAndApplyDynamicLayout() {
+ if (!this.multiColumnLayout || !this.boardElement) return;
+
+ // Debug logging
+ console.log('[Layout Check] Checking dynamic layout...');
+
+ // Check each column to see if scrolling is needed
+ const columns = this.boardElement.querySelectorAll('.column');
+
+ columns.forEach(column => {
+ const columnBody = column.querySelector('.column-body');
+ if (!columnBody) return;
+
+ // Remove dynamic classes first
+ columnBody.classList.remove('dynamic-2-columns', 'dynamic-3-columns');
+ column.classList.remove('expanded-2x', 'expanded-3x');
+
+ // Force reflow to get accurate measurements
+ columnBody.offsetHeight;
+
+ const scrollHeight = columnBody.scrollHeight;
+ const clientHeight = columnBody.clientHeight;
+ const hasOverflow = scrollHeight > clientHeight;
+
+ console.log('[Layout Check]', {
+ column: column.dataset.columnId,
+ scrollHeight,
+ clientHeight,
+ hasOverflow,
+ ratio: scrollHeight / clientHeight
+ });
+
+ // Check if content overflows
+ if (hasOverflow && clientHeight > 0) {
+ // Calculate how many columns we need based on content height
+ const ratio = scrollHeight / clientHeight;
+
+ if (ratio > 2.5 && window.innerWidth >= 1800) {
+ // Need 3 columns
+ console.log('[Layout] Applying 3 columns');
+ columnBody.classList.add('dynamic-3-columns');
+ column.classList.add('expanded-3x');
+ } else if (ratio > 1.1 && window.innerWidth >= 1400) {
+ // Need 2 columns (reduced threshold to 1.1)
+ console.log('[Layout] Applying 2 columns');
+ columnBody.classList.add('dynamic-2-columns');
+ column.classList.add('expanded-2x');
+ }
+ }
+ });
+ }
}
// Create and export singleton
diff --git a/frontend/js/coding.js b/frontend/js/coding.js
index b48ff09..770f71f 100644
--- a/frontend/js/coding.js
+++ b/frontend/js/coding.js
@@ -83,11 +83,8 @@ class CodingManager {
// Farb-Presets
this.renderColorPresets();
- // Name-Eingabe für Pfad-Preview
+ // Name-Eingabe (kein Pfad-Preview mehr nötig)
const nameInput = document.getElementById('coding-name');
- if (nameInput) {
- nameInput.addEventListener('input', () => this.updatePathPreview());
- }
// CLAUDE.md Link Event
const claudeLink = document.getElementById('coding-claude-link');
@@ -165,17 +162,6 @@ class CodingManager {
});
}
- /**
- * Pfad-Preview aktualisieren
- */
- updatePathPreview() {
- const nameInput = document.getElementById('coding-name');
- const preview = document.getElementById('coding-path-preview');
- if (!nameInput || !preview) return;
-
- const name = nameInput.value.trim();
- preview.textContent = name || '...';
- }
// switchClaudeTab entfernt - CLAUDE.md ist jetzt nur readonly
@@ -602,6 +588,7 @@ class CodingManager {
// Felder füllen
document.getElementById('coding-name').value = directory?.name || '';
+ document.getElementById('coding-path').value = directory?.localPath || '';
document.getElementById('coding-description').value = directory?.description || '';
document.getElementById('coding-branch').value = directory?.defaultBranch || 'main';
@@ -609,8 +596,6 @@ class CodingManager {
const claudeContent = directory?.claudeMdFromDisk || '';
this.updateClaudeLink(claudeContent, directory?.name);
- // Pfad-Preview aktualisieren
- this.updatePathPreview();
// Farbe setzen
const color = directory?.color || '#4F46E5';
@@ -663,13 +648,11 @@ class CodingManager {
*/
async handleSave() {
const name = document.getElementById('coding-name').value.trim();
+ const localPath = document.getElementById('coding-path').value.trim();
const description = document.getElementById('coding-description').value.trim();
// CLAUDE.md wird nicht mehr gespeichert - nur readonly
const defaultBranch = document.getElementById('coding-branch').value.trim() || 'main';
- // Pfad automatisch aus Name generieren
- const localPath = `${BASE_PATH}/${name}`;
-
// Farbe ermitteln
const selectedPreset = document.querySelector('.color-preset.selected');
const color = selectedPreset?.dataset.color || document.getElementById('coding-color-custom').value;
@@ -685,6 +668,11 @@ class CodingManager {
return;
}
+ if (!localPath) {
+ showToast('Ordnerpfad ist erforderlich', 'error');
+ return;
+ }
+
const data = {
name,
localPath,
diff --git a/frontend/sw.js b/frontend/sw.js
index 1175f6a..fba1440 100644
--- a/frontend/sw.js
+++ b/frontend/sw.js
@@ -4,7 +4,7 @@
* Offline support and caching
*/
-const CACHE_VERSION = '300';
+const CACHE_VERSION = '306';
const CACHE_NAME = 'taskmate-v' + CACHE_VERSION;
const STATIC_CACHE_NAME = 'taskmate-static-v' + CACHE_VERSION;
const DYNAMIC_CACHE_NAME = 'taskmate-dynamic-v' + CACHE_VERSION;