diff --git a/.vch-description b/.vch-description
new file mode 100644
index 0000000..d8e11cc
--- /dev/null
+++ b/.vch-description
@@ -0,0 +1 @@
+Meeting Mixer – Werkzeug zur fairen, automatischen Einteilung von Moderatoren für wiederkehrende Meetings. Es verteilt die Moderation gleichmäßig über das Team, berücksichtigt Verfügbarkeiten und Urlaube und erstellt fertige Monatspläne zum Export. Für Teams, die ihre Meeting-Moderation organisiert rotieren möchten.
diff --git a/README.md b/README.md
index 3b3f42f..ad758eb 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# Morning Meeting Planner
+# Meeting Mixer
-Web-App zur Planung und Verwaltung von Morning-Meeting-Moderatoren.
+Web-App zur Planung und Verwaltung von Meeting-Moderatoren.
## Features
@@ -12,13 +12,25 @@ Web-App zur Planung und Verwaltung von Morning-Meeting-Moderatoren.
- **Verlauf** – Vergangene Monatspläne einsehen und wiederherstellen
- **Persistenz** – Automatische Speicherung im Browser (localStorage)
+## Tech-Stack
+
+- **Vanilla JavaScript (ES-Module)** – kein Framework, **kein Build-Schritt**
+- **Persistenz:** Browser-`localStorage` (kein Backend, keine Datenbank)
+- **Excel-Im-/Export:** [SheetJS](https://sheetjs.com/) (per CDN, mit SRI eingebunden)
+- **Statisches Serving:** [`serve`](https://www.npmjs.com/package/serve) (Dev)
+- **Tests:** Node-Assertions (`node:assert`), ohne externe Abhängigkeiten
+
+Voraussetzung: ein moderner Browser mit ES-Modul-Unterstützung.
+
## Starten
```bash
npm start
```
-Die App läuft auf **http://localhost:3001**.
+Startet einen statischen Server. Der genutzte Port wird beim Start ausgegeben
+(Standard 3001; weicht automatisch aus, falls belegt). Öffne anschließend die
+angezeigte URL im Browser.
## Tests
diff --git a/index.html b/index.html
index 372531e..3dfff57 100644
--- a/index.html
+++ b/index.html
@@ -4,7 +4,7 @@
-
Morning Meeting Planner
+ Meeting Mixer
diff --git a/js/algorithm.js b/js/algorithm.js
index b7bddfe..64a88cf 100644
--- a/js/algorithm.js
+++ b/js/algorithm.js
@@ -1,5 +1,8 @@
import { toISO, getWeekday } from './utils.js';
+// Score penalty applied when a candidate is low-priority on the given weekday.
+const LOW_PRIORITY_PENALTY = 5;
+
function isOnVacation(employee, isoDate) {
return employee.constraints.vacations.some(v => v.from <= isoDate && isoDate <= v.to);
}
@@ -60,7 +63,7 @@ function scoreCandidate(employee, isoDate, histCount, randomFn) {
let score = -myTotal;
// Low priority: increase negative score (less likely to be picked)
- if (employee.constraints.lowPriorityDays.includes(wd)) score -= 5;
+ if (employee.constraints.lowPriorityDays.includes(wd)) score -= LOW_PRIORITY_PENALTY;
// Jitter ±0.49
score += (randomFn() - 0.5) * 0.98;
diff --git a/js/app.js b/js/app.js
index db0463f..a7b55d3 100644
--- a/js/app.js
+++ b/js/app.js
@@ -2,6 +2,7 @@ import { renderEmployees } from './employees.js';
import { renderCalendar } from './calendar.js';
import { renderDataTab } from './history.js';
import { normalizeEmployees } from './data.js';
+import { MIN_MODERATORS_PER_DAY, MAX_MODERATORS_PER_DAY } from './utils.js';
const STORAGE_KEY = 'morning-planner-state';
@@ -22,7 +23,8 @@ try {
if (parsed.calendar) state.calendar = parsed.calendar;
if (Array.isArray(parsed.history)) state.history = parsed.history;
if (parsed.settings && Number.isInteger(parsed.settings.moderatorsPerDay)
- && parsed.settings.moderatorsPerDay >= 1 && parsed.settings.moderatorsPerDay <= 5) {
+ && parsed.settings.moderatorsPerDay >= MIN_MODERATORS_PER_DAY
+ && parsed.settings.moderatorsPerDay <= MAX_MODERATORS_PER_DAY) {
state.settings = { moderatorsPerDay: parsed.settings.moderatorsPerDay };
}
}
diff --git a/js/calendar.js b/js/calendar.js
index 01bfcbb..edc1242 100644
--- a/js/calendar.js
+++ b/js/calendar.js
@@ -2,7 +2,8 @@ import { state, saveState } from './app.js';
import { getWorkdays, generatePlan } from './algorithm.js';
import { toggleHoliday, toggleClosure, addHistoryEntries, removeHistoryEntry } from './data.js';
import { exportSchedule, downloadBlob } from './excel.js';
-import { toISO, monthPrefix, getWeekday, buildNameMap } from './utils.js';
+import { toISO, monthPrefix, getWeekday, buildNameMap,
+ MIN_MODERATORS_PER_DAY, MAX_MODERATORS_PER_DAY } from './utils.js';
const MONTHS = ['Januar','Februar','März','April','Mai','Juni',
'Juli','August','September','Oktober','November','Dezember'];
@@ -61,7 +62,7 @@ export function renderCalendar() {
const slotsSel = document.createElement('select');
slotsSel.id = 'cal-slots';
- for (let n = 1; n <= 5; n++) {
+ for (let n = MIN_MODERATORS_PER_DAY; n <= MAX_MODERATORS_PER_DAY; n++) {
const opt = document.createElement('option');
opt.value = String(n);
opt.textContent = String(n);
diff --git a/js/data.js b/js/data.js
index af47ae6..5f42fe9 100644
--- a/js/data.js
+++ b/js/data.js
@@ -1,3 +1,5 @@
+import { MIN_MODERATORS_PER_DAY, MAX_MODERATORS_PER_DAY } from './utils.js';
+
function generateId() {
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
return crypto.randomUUID();
@@ -17,8 +19,8 @@ function generateId() {
function parseSettings(raw) {
const n = raw?.moderatorsPerDay;
- const valid = Number.isInteger(n) && n >= 1 && n <= 5;
- return { moderatorsPerDay: valid ? n : 1 };
+ const valid = Number.isInteger(n) && n >= MIN_MODERATORS_PER_DAY && n <= MAX_MODERATORS_PER_DAY;
+ return { moderatorsPerDay: valid ? n : MIN_MODERATORS_PER_DAY };
}
function defaultConstraints() {
diff --git a/js/utils.js b/js/utils.js
index bd5da79..ba78e0a 100644
--- a/js/utils.js
+++ b/js/utils.js
@@ -1,4 +1,9 @@
-// Shared pure helpers used across modules.
+// Shared pure helpers and constants used across modules.
+
+// Allowed range for the "moderators per day" setting.
+export const MIN_MODERATORS_PER_DAY = 1;
+export const MAX_MODERATORS_PER_DAY = 5;
+
// "YYYY-MM-DD" from numeric parts.
export function toISO(year, month, day) {
diff --git a/package.json b/package.json
index 124ec8d..a13c7b5 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
{
- "name": "morning-meeting-planner",
+ "name": "meeting-mixer",
"version": "1.0.0",
"private": true,
"type": "module",