diff --git a/CHANGELOG.md b/CHANGELOG.md
index 95ac2e0..0c24a51 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+## [0.9.0] - 2026-04-04
+
+### Added
+- Optional task priority: new "None" level allows tasks without urgency, reducing visual noise for routine tasks (#15)
+- "None" is now the default priority for new tasks
+- Tasks with no priority hide the priority badge entirely in list and dashboard views
+- DB migration v4 extends priority CHECK constraint to include 'none'
+- i18n keys for "None" priority in de, en, it locales
+
## [0.8.2] - 2026-04-04
### Fixed
diff --git a/package.json b/package.json
index dd6944b..09b4f6e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "oikos",
- "version": "0.8.2",
+ "version": "0.9.0",
"description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.",
"main": "server/index.js",
"type": "module",
diff --git a/public/locales/de.json b/public/locales/de.json
index a6f7341..9a15f32 100644
--- a/public/locales/de.json
+++ b/public/locales/de.json
@@ -100,6 +100,7 @@
"priorityHigh": "Hoch",
"priorityMedium": "Mittel",
"priorityLow": "Niedrig",
+ "priorityNone": "Keine",
"statusOpen": "Offen",
"statusInProgress": "In Bearbeitung",
"statusDone": "Erledigt",
diff --git a/public/locales/en.json b/public/locales/en.json
index be1d695..1a4d49b 100644
--- a/public/locales/en.json
+++ b/public/locales/en.json
@@ -100,6 +100,7 @@
"priorityHigh": "High",
"priorityMedium": "Medium",
"priorityLow": "Low",
+ "priorityNone": "None",
"statusOpen": "Open",
"statusInProgress": "In Progress",
"statusDone": "Done",
diff --git a/public/locales/it.json b/public/locales/it.json
index 283e5b2..a7d8c17 100644
--- a/public/locales/it.json
+++ b/public/locales/it.json
@@ -100,6 +100,7 @@
"priorityHigh": "Alta",
"priorityMedium": "Media",
"priorityLow": "Bassa",
+ "priorityNone": "Nessuna",
"statusOpen": "Aperto",
"statusInProgress": "In corso",
"statusDone": "Completato",
diff --git a/public/pages/dashboard.js b/public/pages/dashboard.js
index d5a2cdd..90ce761 100644
--- a/public/pages/dashboard.js
+++ b/public/pages/dashboard.js
@@ -167,7 +167,7 @@ function renderUrgentTasks(tasks) {
const due = formatDueDate(t.due_date);
return `
-
+ ${t.priority !== 'none' ? `
` : ''}
${PRIORITY_LABELS()[t.priority] ?? t.priority}
${esc(t.title)}
diff --git a/public/pages/tasks.js b/public/pages/tasks.js
index 54427a3..29f6237 100644
--- a/public/pages/tasks.js
+++ b/public/pages/tasks.js
@@ -20,6 +20,7 @@ const PRIORITIES = () => [
{ value: 'high', label: t('tasks.priorityHigh'), color: 'var(--color-priority-high)' },
{ value: 'medium', label: t('tasks.priorityMedium'), color: 'var(--color-priority-medium)' },
{ value: 'low', label: t('tasks.priorityLow'), color: 'var(--color-priority-low)' },
+ { value: 'none', label: t('tasks.priorityNone'), color: 'var(--color-priority-none)' },
];
const STATUSES = () => [
@@ -110,6 +111,7 @@ function groupBy(tasks, mode) {
// --------------------------------------------------------
function renderPriorityBadge(priority) {
+ if (priority === 'none') return '';
return `
${PRIORITY_LABELS()[priority] ?? priority}
@@ -254,7 +256,7 @@ function renderModalContent({ task = null, users = [] } = {}) {
).join('');
const priorityOptions = PRIORITIES().map((p) =>
- ``
+ ``
).join('');
return `
diff --git a/public/styles/tokens.css b/public/styles/tokens.css
index 5eb8ef6..05420f2 100644
--- a/public/styles/tokens.css
+++ b/public/styles/tokens.css
@@ -114,12 +114,14 @@
/* --------------------------------------------------------
* 6. Farben - Prioritäten
* -------------------------------------------------------- */
+ --color-priority-none: var(--neutral-400);
--color-priority-low: var(--neutral-500);
--color-priority-medium: #B45309;
--color-priority-high: #D4511E;
--color-priority-urgent: #DC2626;
/* Hintergrundfarben für Priority-Badges */
+ --color-priority-none-bg: rgba(142, 141, 137, 0.08);
--color-priority-low-bg: rgba(142, 141, 137, 0.12);
--color-priority-medium-bg: rgba(180, 83, 9, 0.12);
--color-priority-high-bg: rgba(212, 81, 30, 0.12);
@@ -328,6 +330,7 @@
--meal-snack-light: #3D2010;
/* Priority-Badge Hintergründe */
+ --color-priority-none-bg: rgba(142, 141, 137, 0.12);
--color-priority-low-bg: rgba(142, 141, 137, 0.18);
--color-priority-medium-bg: rgba(230, 147, 10, 0.18);
--color-priority-high-bg: rgba(212, 81, 30, 0.18);
@@ -408,6 +411,7 @@
--meal-dinner-light: #1A2D4D;
--meal-snack-light: #3D2010;
+ --color-priority-none-bg: rgba(142, 141, 137, 0.12);
--color-priority-low-bg: rgba(142, 141, 137, 0.18);
--color-priority-medium-bg: rgba(230, 147, 10, 0.18);
--color-priority-high-bg: rgba(212, 81, 30, 0.18);
diff --git a/public/sw.js b/public/sw.js
index 67b618b..8ce6352 100644
--- a/public/sw.js
+++ b/public/sw.js
@@ -12,9 +12,9 @@
* API: Immer Netzwerk (kein Caching von Nutzerdaten)
*/
-const SHELL_CACHE = 'oikos-shell-v23';
-const PAGES_CACHE = 'oikos-pages-v23';
-const ASSETS_CACHE = 'oikos-assets-v23';
+const SHELL_CACHE = 'oikos-shell-v24';
+const PAGES_CACHE = 'oikos-pages-v24';
+const ASSETS_CACHE = 'oikos-assets-v24';
const ALL_CACHES = [SHELL_CACHE, PAGES_CACHE, ASSETS_CACHE];
// App-Shell: sofort benötigt für ersten Render
diff --git a/server/db.js b/server/db.js
index b72399c..1a1269c 100644
--- a/server/db.js
+++ b/server/db.js
@@ -88,8 +88,8 @@ const MIGRATIONS = [
title TEXT NOT NULL,
description TEXT,
category TEXT NOT NULL DEFAULT 'Sonstiges',
- priority TEXT NOT NULL DEFAULT 'medium'
- CHECK(priority IN ('low', 'medium', 'high', 'urgent')),
+ priority TEXT NOT NULL DEFAULT 'none'
+ CHECK(priority IN ('none', 'low', 'medium', 'high', 'urgent')),
status TEXT NOT NULL DEFAULT 'open'
CHECK(status IN ('open', 'in_progress', 'done')),
due_date TEXT,
@@ -296,6 +296,41 @@ const MIGRATIONS = [
CREATE INDEX IF NOT EXISTS idx_budget_parent ON budget_entries(recurrence_parent_id);
`,
},
+ {
+ version: 4,
+ description: 'Priorität "none" erlauben und als Default setzen',
+ up: `
+ -- SQLite erlaubt kein ALTER CHECK, daher Tabelle neu erstellen
+ CREATE TABLE tasks_new (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ title TEXT NOT NULL,
+ description TEXT,
+ category TEXT NOT NULL DEFAULT 'Sonstiges',
+ priority TEXT NOT NULL DEFAULT 'none'
+ CHECK(priority IN ('none', 'low', 'medium', 'high', 'urgent')),
+ status TEXT NOT NULL DEFAULT 'open'
+ CHECK(status IN ('open', 'in_progress', 'done')),
+ due_date TEXT,
+ due_time TEXT,
+ assigned_to INTEGER REFERENCES users(id) ON DELETE SET NULL,
+ created_by INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ is_recurring INTEGER NOT NULL DEFAULT 0,
+ recurrence_rule TEXT,
+ parent_task_id INTEGER REFERENCES tasks(id) ON DELETE CASCADE,
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
+ );
+
+ INSERT INTO tasks_new SELECT * FROM tasks;
+ DROP TABLE tasks;
+ ALTER TABLE tasks_new RENAME TO tasks;
+
+ CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
+ CREATE INDEX IF NOT EXISTS idx_tasks_assigned ON tasks(assigned_to);
+ CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_task_id);
+ CREATE INDEX IF NOT EXISTS idx_tasks_due ON tasks(due_date);
+ `,
+ },
];
/**
diff --git a/server/routes/tasks.js b/server/routes/tasks.js
index 6492e87..bbd156e 100644
--- a/server/routes/tasks.js
+++ b/server/routes/tasks.js
@@ -18,7 +18,7 @@ const router = express.Router();
// Konstanten
// --------------------------------------------------------
-const VALID_PRIORITIES = ['low', 'medium', 'high', 'urgent'];
+const VALID_PRIORITIES = ['none', 'low', 'medium', 'high', 'urgent'];
const VALID_STATUSES = ['open', 'in_progress', 'done'];
const VALID_CATEGORIES = ['Haushalt', 'Schule', 'Einkauf', 'Reparatur',
'Gesundheit', 'Finanzen', 'Freizeit', 'Sonstiges'];
@@ -96,7 +96,7 @@ router.get('/', (req, res) => {
ORDER BY
CASE t.status WHEN 'done' THEN 1 ELSE 0 END,
CASE t.priority WHEN 'urgent' THEN 0 WHEN 'high' THEN 1
- WHEN 'medium' THEN 2 ELSE 3 END,
+ WHEN 'medium' THEN 2 WHEN 'low' THEN 3 ELSE 4 END,
t.due_date ASC NULLS LAST,
t.created_at DESC
`;
@@ -148,7 +148,7 @@ router.post('/', (req, res) => {
title,
description = null,
category = 'Sonstiges',
- priority = 'medium',
+ priority = 'none',
due_date = null,
due_time = null,
assigned_to = null,