_flush() loeschte KEY_PENDING unconditionally nach erfolgreichem PUT. Lief
waehrend des PUT ein weiterer push() (neue Aenderung B), wurde deren pending-
Status vom ersten Flush geloescht, obwohl B noch nicht auf dem Server ist.
Schloss der Nutzer den Tab davor, sah der naechste boot() pending=false und
uebernahm den Server-Stand -> B ging verloren.
Fix: Generationszaehler _dirty. push() inkrementiert ihn, _flush() merkt sich
den Stand vor dem await und loescht pending nur, wenn kein neuerer push()
in der Zwischenzeit lief.
Neue Tests in sync.test.js decken Race (pending bleibt gesetzt) und
Normalfall (pending wird geleert) ab.
Update-Prozedur in CLAUDE.md auf volume-aware docker run umgestellt
(-v dienstplan-data:/data, -e DATA_DIR=/data) und Dockerfile-Snippet
auf den neuen Express/SQLite-Server aktualisiert. Hinweis ergaenzt,
dass DB und Backups auf dem Volume liegen und die Domain hinter Caddy
Basic-Auth steht.
Zusaetzlich .dockerignore ergaenzt: node_modules aus dem Host-Kontext
wurde sonst per COPY . . ins Image kopiert und ueberschrieb das im
Image gebaute better-sqlite3-Binary (invalid ELF header beim Start).
Ohne diesen Fix startet der Container nicht.
V1/V2/V3 duplicated the empty-result shape, the paid-shares/bonus math
and the Friday-priority deduction loop. Pull them into ineligibleResult,
payResult and deductInOrder; each variant keeps only its distinct
threshold, eligibility test and deduction slots.
paidShares is now computed uniformly as max(0, classified - deduction);
never-deducted slots carry deduction 0, so the result is byte-identical
(verified against a 13122-case golden master over the full input grid).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each variant emitted isWinner:false and the calculator set winner.isWinner
=true, but the UI marks the winner via variantId===winnerId, never the
flag. Only one test read it. Removed from the 6 variant returns, the empty
result, and the calculator.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
No UI reads them - app.js computes its own labels via isQualifyingDay/
getDayTypeLabel when rendering the duty list. Only the test-suite asserted
them; those asserts are removed too. buildDutyDetails goes with them.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
groupEntriesByResolvedEmployee and resolveImports carried an identical
resolvedNames -> ignore/new/assign branch. One helper returning
{resolved, isNew}; callers keep their own null/newEmployee handling.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "loop employees -> getVacationMode -> calculateAllEmployees" block
was copy-pasted in calculateBonuses, generateEmailReport, exportCSV and
exportBonusReport. Collapse to one storage helper. Behaviour identical:
calculateAllEmployees only reads keys present in employeeDuties.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
new Date() and the object spread never throw; the isNaN guard already
drops invalid dates via the .filter(). The inner catch could never fire.
Outer boundary try/catch is kept.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Zero callers across app, calculator, image-import and tests. Holiday
lookup goes through isHoliday/getHolidayName only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Same-day duty overwrite: addDuty is keyed per employee+date, so a
split Saturday duty between two employees creates two independent
0.5 entries, not a conflict. Not a bug.
- Holiday cutoff 2030: not a current priority, dropped per user call.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The eligible-over-ineligible tie-break in calculateMonthlyBonus was a
deliberate fix (commit c733c3e) covered by an explicit test
(test-suite.js:191-203). Flagging it as an open bug in TODO.md was a
misreading of calculator.js against the design spec's simplified
pseudocode; the spec's own test plan (section 12.5) and the actual
test suite confirm the current behavior is correct.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Tie-breaker in calculateMonthlyBonus deviates from spec section 5,
same-day duties overwrite instead of summing, and holiday data only
covers 2025-2030.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Remove broken webpack.yml (no package.json/webpack config exists)
- Drop dead RATE_NORMAL/RATE_WEEKEND fields on BonusCalculator; share the
real rate constants across all three variants in variants.js
- Simplify getEmptyResult() to build allResults via .map instead of
three manual spreads
- Replace 5 duplicated German month-name arrays and 2 weekday-name
arrays in app.js with a shared monthName()/WEEKDAY_NAMES helper;
drop the now-redundant getMonthName() method
- Reuse existing .modal/.modal-backdrop/.modal-content CSS classes in
generateEmailReport() instead of hand-rolled inline styles
- Delegate ImageImporter.classify() to the shared classify() in
variants.js instead of a duplicated copy
- Unify ad hoc YYYY-MM-DD formatting in app.js to use the existing
HolidayProvider.formatDate() helper
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Multi-tenant deploy policies, container/Quadlet hardening rules, and
ADR/SOP templates don't fit a single-maintainer static-site app with
one Dockerfile and no container orchestration.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adopt the org engineering standard (its-consulting/standards @ v1.0.0).
Adds baseline governance/CI/policy files rendered from the standard's
templates and pins .standards-version=1.0.0. Vendored OPA/Rego policies
under .standards/policies/ so CI runs the gate locally (no cross-repo dep).
Placeholders ({{ORG}}/{{REPO}}/{{OWNER_HANDLE}}/{{MAINTAINER_EMAIL}}) filled in.
Existing files that differ were left untouched by the adopter.
Automated rollout. Files created: 16.
Adopt the org engineering standard (its-consulting/standards @ v1.0.0).
Adds baseline governance/CI/policy files rendered from the standard's
templates and pins .standards-version=1.0.0. Vendored OPA/Rego policies
under .standards/policies/ so CI runs the gate locally (no cross-repo dep).
Placeholders ({{ORG}}/{{REPO}}/{{OWNER_HANDLE}}/{{MAINTAINER_EMAIL}}) filled in.
Existing files that differ were left untouched by the adopter.
Automated rollout. Files created: 16.
The bild-import and bonus-varianten plans were step-by-step build guides
consumed during implementation. Both features are merged and live.
Design specs in docs/specs/ stay for reference.
Conflicts resolved:
- sw.js: bumped CACHE_NAME to dienstplan-pro-v4 (was v3 + v2). Both
variants.js and image-import.js are in ASSETS.
- storage.js: kept STORAGE_KEY_DUTIES + STORAGE_KEY_VACATION (Feature B)
alongside STORAGE_KEY_OPENROUTER_KEY/MODEL + DEFAULT_MODEL (Feature A).
- styles.css: appended Feature B variants/vacation/date-stepper rules
after Feature A modal/key rules; both blocks coexist.
`let app` at top level does not create a window property, so the
DOMContentLoaded handler in image-import.js found window.app undefined
and never instantiated window.imageImporter. Clicking the Bild-Import
button then showed the 'Bild-Import nicht verfuegbar' toast.
Set window.app explicitly after construction.
Winner-picker used strict > with lowest-variantId tie-break, which made
V1 (ineligible) win over V3 (eligible) when all variants produced bonus=0
on Sa+So-only duties. Now ties prefer the eligible variant, keeping the
lowest-variantId rule as a sub-tie-breaker.