Commit Graph
360 Commits
Author SHA1 Message Date
Nick SzaunigandClaude Opus 5.5 2656fb8328 fix(ui): number keyboard on phones for every weight, amount and percentage field
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 18:38:55 +02:00
Nick SzaunigandClaude Opus 5.5 c3dc596170 feat(ui): honey batch page with one main action, traceability errors, confirmed removal
- DetailHeader: "Abfüllung anlegen" as the main action, PDF export in
  "Mehr", state as icon + word, retry and way back on load errors.
- Traceability says what it shows and offers a retry on errors instead
  of "Lädt…" forever.
- Removing a container allocation asks first and says what changes.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 18:37:37 +02:00
Nick SzaunigandClaude Opus 5.5 86f9291ee3 feat(ui): harvest, extraction, bottling and equipment pages with heading and way back
Same DetailHeader as colonies/queens: an h1, a 44 px back link, state as
icon + word, the obvious next step as the one primary button (harvest:
"Schleuderung erfassen"); a failed load offers a retry and a way back
instead of a bare red line. Colonies of a harvest are tappable links;
changing equipment condition confirms with a toast.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 18:36:39 +02:00
Nick SzaunigandClaude Opus 5.5 d4b1addb7d fix(security): keep form drafts per user and delete them on logout
Drafts were stored in localStorage by form only, so on a shared phone
the next person was offered the previous user's unfinished entry
(colony observations, treatment and supplier details). Drafts are now
keyed by the signed-in user id and every draft is removed on logout,
like the offline cache already was.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 18:33:38 +02:00
Nick SzaunigandClaude Opus 5.5 031e47eaad feat(ui): detail pages for colonies, apiaries and queens with one clear main action
New DetailHeader: heading, back link, status as icon + word, ONE primary
action and a "Mehr" menu for exports, QR and the rest.

- Colony: "Durchsicht erfassen" is the main action; sub-cards show a
  load error with retry instead of "Noch keine …".
- Apiary: error states, weather with units.
- Queen: edit card in sections, evaluations as 1-5 tap scales.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 18:32:33 +02:00
Nick SzaunigandClaude Opus 5.5 2bc0db6f33 fix(statistics): feeding totals per unit instead of adding litres to kilograms
The feeding statistics summed every amount regardless of its unit, so
5 l syrup and 2 kg fondant showed as 7. The API now also returns byUnit
(units normalised: "L" = "l"), the old fields stay for compatibility,
and the statistics page shows one list per unit.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 12:27:24 +02:00
Nick SzaunigandClaude Opus 5.5 ed0a000534 feat(ui): remaining capture forms with sections, tap choices and drafts
Varroa, swarm control, harvest, extraction, bottling, task and queen
forms use the form kit: required fields marked, inline errors with focus
on the first problem, rare fields folded, drafts, save in thumb reach.
Single choices (varroa method, swarm mood/action, priority, marking
colour) are large tap buttons; colonies and harvests are picked as
tiles. Plain hints: per-day mite fall vs. threshold, Brutto/Netto,
water content, MHD and lot number, marking colour by year.

Fixed on the way: the queen form suggested the marking colour from the
first digit typed ("2" -> Gelb for 2026); it now waits for a full year.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 12:25:24 +02:00
Nick SzaunigandClaude Opus 5.5 49f3db5b56 feat(ui): storage, map, search, wintering and statistics without dead ends
- Every query shows loading, an error with "Erneut versuchen", an empty
  state with a next step, then data; statistics no longer hang on
  "Lädt…" after an error, a failed search no longer reads as "no hits".
- Storage: one primary action, stock items as a clearly secondary
  section, low stock as icon + word.
- Wintering: "Gassen" explained, outcome as icon + word, touch-size
  edit buttons; map: tells how many apiaries have coordinates.
- Statistics: units and plain words instead of "Ø", a select instead
  of crowded tabs on phones.
- QR code buttons are touch size.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 12:22:32 +02:00
Nick SzaunigandClaude Opus 5.5 0e46f666aa fix(ui): backup schedule shows a load error with retry, not "not configured"
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 12:17:56 +02:00
Nick SzaunigandClaude Opus 5.5 d57162e709 feat(ui): undo for deleted label elements, warn before leaving unsaved changes
The Delete key removed a label element for good; it now shows a toast
with "Rückgängig" that puts it back in place. Closing or reloading the
designer with unsaved changes asks first.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 12:17:36 +02:00
Nick SzaunigandClaude Opus 5.5 b4c8d80c33 fix(ui): number keyboard for intervals in recurring task dialogs
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 12:16:19 +02:00
Nick SzaunigandClaude Opus 5.5 549a75988e feat(ui): webhooks as cards with events in words, calendar sync without env jargon
- Webhooks: one card per connection, events by their German label
  instead of keys like "product.stock_changed", state as icon + word,
  a one-sentence explanation of what a webhook is for, retry on errors.
- Calendar sync: the group id field says where to find it; the
  "not configured" note says who sets it up instead of listing
  environment variable names.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 12:15:54 +02:00
Nick SzaunigandClaude Opus 5.5 a21b1665e7 feat(ui): colony edit dialogs with sections and tap ratings, confirmed merge
Inspection, treatment, feeding, varroa and swarm-control edit dialogs use
the form kit (required markers, inline errors, 1-5 as buttons, yes/no
tiles, number keyboards, save button pinned at the dialog bottom).
Moving, splitting and merging colonies report success; merging - which
cannot be undone - asks first and says what happens.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 12:14:07 +02:00
Nick SzaunigandClaude Opus 5.5 9944aabbc3 feat(ui): list pages with retry on errors, a next step when empty, one main action
Lists load through QueryState: a failed load says so and offers
"Erneut versuchen" instead of looking empty; empty lists link to the
create page; exports move into a "Mehr" menu so "Neu …" is the one
primary action.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 12:14:07 +02:00
Nick SzaunigandClaude Opus 5.5 62e78498bf feat(ui): capture forms for treatments, feedings, colonies, apiaries, products, equipment
Same structure as the inspection form: required fields marked, errors at
the field, numbered sections with rarely used fields folded, drafts kept
on the device, save button in thumb reach, plain hints for jargon
(Wirkstoff, Wartezeit, Code, SKU, units). The Bestandsbuch fields of a
treatment stay a visible section of their own. Apiaries can take the
coordinates from the phone's location. Offline queue and idempotency
are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 12:14:07 +02:00
Nick SzaunigandClaude Opus 5.5 832997f885 feat(ui): users and audit log as readable cards with names and retry
- Users: one card per person on every screen size, status and MFA as
  icon plus word, server reasons (e.g. "more rights than you") shown.
- Audit log: names instead of user ids, area as a choice instead of a
  free-text type, optional record id, filter reset, cards instead of a
  5-column table, empty and error states with a next step.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 12:13:40 +02:00
Nick SzaunigandClaude Opus 5.5 bd2070436c feat(ui): calendar as a day list on phones, today named, every entry reachable
- Phones get a list of the days that have entries (44 px rows with type
  word, icon and title) instead of a 7-column grid with 0.7 rem chips.
- Month grid on wider screens: days are buttons, the chosen day lists
  all its entries below, so "+N weitere" is no longer a dead end.
- Today is marked with a filled circle and the word "Heute"; a "Heute"
  button jumps back. Month navigation buttons are touch size.
- Load errors offer a retry; an empty month says so.
- Entries are grouped by local day instead of UTC, so late-evening
  entries no longer land on the next day.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 07:38:09 +02:00
Nick SzaunigandClaude Opus 5.5 dff6c364c8 feat(ui): automation settings start with everyday tabs and confirm risky actions
- Tabs ordered from everyday (suggestions, recurring tasks, labels) to
  technical (calendar sync, API keys, webhooks); the page opens on
  "Vorschläge" instead of Webhooks, and says in one sentence what it is.
- Resetting a number counter, deactivating a label template and ending a
  recurring task now ask first and say plainly what happens.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 07:36:28 +02:00
Nick SzaunigandClaude Opus 5.5 78325af1fe feat(ui): touch-size controls, scrollable dialogs, motion levels everywhere, load states
- Inputs and selects are 44 px on phones (36 px from sm up); default and
  small buttons get 40/36 px on phones.
- Dialogs scroll inside the viewport, so long edit forms keep their
  save button reachable on a phone.
- Dialog zoom, sheet slide, sidebar icon scale and stat-bar growth only
  move at the motion level "Voll" (motion-full), not at "Dezent".
- QueryState / ErrorState / LoadingState: loading, error with "Erneut
  versuchen", empty, data - a failed load can no longer look like an
  empty list. EmptyState takes an action for the obvious next step.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 07:33:50 +02:00
Nick SzaunigandClaude Opus 5.5 32dfd8d8a7 feat(ui): calmer inspection form with sections, tap ratings and drafts
AuDHD-oriented rework of the most-used form, plus shared building blocks
for the remaining forms:

- Field: label, "Pflicht"/"optional", hint and the error right at the
  field instead of a vanishing toast.
- FormSection: numbered steps; optional parts start folded and say how
  many fields they hold.
- RatingScale (1-5 as five 44 px buttons with both ends named),
  ToggleChip (yes/no tiles with tick and colour), StickySubmit (save
  stays in thumb reach on phones).
- useFormDraft + DraftBanner: an unfinished entry is kept on the device
  and offered back explicitly ("Weitermachen" / "Verwerfen"), never
  applied silently; drafts expire after 3 days.

Inspection form: 21 flat fields became 5 steps, only colony and date are
required, observations are single taps, a hint appears when neither queen
nor eggs were seen, server messages show inline.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 07:32:20 +02:00
Nick SzaunigandClaude Opus 5.5 8b6f48b996 docs: API security model, audit results and operating notes
Auth flow and guard order as built now, new architecture decisions
(user-management escalation guard, SSO identity binding, sealed backups),
a section on API security with the remaining known gaps, and the audit
table with commits, new env variables and steps after the update.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 07:27:22 +02:00
Nick SzaunigandClaude Opus 5.5 45efb08369 fix(auth): one-time SSO and MFA tokens, permission changes effective on every replica
- Every JWT carries a jti. The SSO completion token and the MFA challenge
  (once a code was accepted) are recorded as used in ConsumedToken; a
  replayed request or copied cookie cannot open a second session. Used
  ids are cleaned up after they expire.
- The token-validity check (active user, active role, password change)
  reads fresh on every request and refreshes the permission cache, so a
  deactivation or role change made on another replica applies to the very
  next request instead of up to 30 s later.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 07:24:28 +02:00
Nick SzaunigandClaude Opus 5.5 fc6af8737b style(public): drop a needless escape in the lot number pattern
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 07:20:55 +02:00
Nick SzaunigandClaude Opus 5.5 0899576262 fix(api): tighter public lot endpoint, SSO callback from config, mapped 4xx errors
- Public lot lookup: 30 requests/min per IP and only well-formed lot
  numbers (1-64 chars) reach the database.
- SSO callback URL is built from OIDC_REDIRECT_URI instead of the Host
  header; SSO cookies are cleared with the attributes they were set with.
- Logout is rate-limited like the other auth endpoints.
- Middleware errors with a 4xx status (body too large, malformed JSON)
  answer with that status instead of a 500; error log lines carry the
  path without query string.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 07:20:33 +02:00
Nick SzaunigandClaude Opus 5.5 eafe925d60 fix(search): only search record kinds the caller may read
search:read alone returned apiary names, colony and queen codes, task
titles and batch numbers to roles without the matching read permission.
Each kind of result is now searched only when apiaries:read,
colonies:read, queens:read, tasks:read or honey-batches:read is held.
The search term is validated (string, at most 100 characters).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 07:18:33 +02:00
Nick SzaunigandClaude Opus 5.5 0ba8996e3a feat(auth): 12-character minimum, 72-byte maximum and breach check for new passwords
- New passwords (create user, admin reset, own change) need at least 12
  characters (ASVS 2.1.1) and may not exceed 72 bytes, which bcrypt would
  silently cut off.
- They are checked against Have I Been Pwned with k-anonymity (only the
  first 5 SHA-1 characters leave the server, padded responses); an
  unreachable service is logged and does not block. PASSWORD_BREACH_CHECK
  =false disables it for offline deployments.
- Login accepts existing passwords as before, capped at 256 characters to
  bound anonymous bcrypt work.
- Password fields in the UI say the rule up front.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 02:47:09 +02:00
Nick SzaunigandClaude Opus 5.5 61e32474e4 fix(export): neutralize spreadsheet formulas in CSV exports
Typed-in values (colony codes, batch numbers, honey types) starting with
= + - @ TAB or CR were written as-is and ran as formulas (HYPERLINK, DDE)
when the export was opened in Excel or LibreOffice. Such text cells now
get a leading apostrophe; numbers keep their sign. Carriage returns are
quoted as well.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 02:43:44 +02:00
Nick SzaunigandClaude Opus 5.5 f1dc0de3e2 feat(api-keys): restrict keys to source IP addresses
A key can be limited to IP addresses or CIDR networks (e.g. the Wabenhain
server); a stolen key is useless from anywhere else. Requests from other
addresses get 403 "not allowed for this IP" and a log line. The admin UI
asks for the addresses when creating a key, warns when a key works from
anywhere, shows the last source address and can take it over into the
allowlist - changing the list needs no new key (PATCH /api-keys/:id).

Also: a key can only carry scopes its creator holds.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 02:41:46 +02:00
Nick SzaunigandClaude Opus 5.5 9ae1d96a32 fix(api): throttle before auth, configurable proxy trust, no-store and a strict CSP
- ThrottlerGuard runs first, so requests with wrong tokens or API keys
  count against the rate limit before any database lookup.
- TRUST_PROXY (hop count or Express list) replaces the hard-coded 1.
- Every API answer except the public lot pages is Cache-Control: no-store
  without ETag; helmet sends default-src 'none' / frame-ancestors 'none'
  (Swagger keeps the page policy when enabled) and same-site CORP.
- Request log lines carry the path only - no query strings (the SSO
  callback's code and state no longer reach the logs).
- In production the server refuses to start without an https
  FRONTEND_ORIGIN or with COOKIE_SECURE=false.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 02:37:31 +02:00
Nick SzaunigandClaude Opus 5.5 2de93bc087 fix(auth): atomic refresh rotation and a 7-day absolute session limit
- Rotation claims the old token with a conditional update; of two
  concurrent refreshes only one gets a successor, so a stolen token cannot
  fork into two live sessions.
- A token rotated out less than 10 s ago (parallel tab) is refused without
  revoking every session; later reuse still revokes them all.
- Sessions end at most SESSION_MAX_AGE (default 7 days) after login: the
  login time is inherited by every rotated token and caps its expiry.
- The refresh cookie is cleared with the same attributes it was set with.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 02:30:32 +02:00
Nick SzaunigandClaude Opus 5.5 5c666fe49a fix(auth): lock accounts after failed attempts, one-time TOTP codes, stronger MFA changes
- Per-account lockout: five failed attempts (password, MFA code or a
  step-up confirmation) lock the account for 15 minutes, on top of the
  per-IP throttle - guesses spread over many addresses no longer help.
- A TOTP code is accepted once: the time step is stored and that step or
  any earlier one is refused (also between two concurrent requests).
- Login runs bcrypt on every path and answers unknown email, wrong
  password, deactivated and locked accounts with the same message; failed
  logins for unknown emails no longer write attacker-chosen text into the
  audit log.
- Turning MFA off needs password and a current code; turning it on needs
  the password. Both end all other sessions.
- New audit action ACCOUNT_LOCKED.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 02:27:47 +02:00
Nick SzaunigandClaude Opus 5.5 79d20ee64e fix(webhooks): close DNS rebinding, time out deliveries, sign with timestamp
- Deliveries connect through a lookup hook that checks the address
  actually dialled, so a host that resolves to a private address after
  registration (DNS rebinding) or a tampered row cannot reach internal
  services. Redirects are still not followed.
- More blocked ranges (CGNAT/100.64/10 incl. cloud metadata, multicast,
  reserved, benchmarking, NAT64, 6to4, site-local, IPv4-mapped IPv6);
  only ports 80/443, no credentials in the URL, https in production.
- 5 s timeout, response body discarded; dispatch no longer waits for
  receivers, so a hanging endpoint cannot stall the user's request.
- Signature covers "timestamp.deliveryId.body" with X-ApiCore-Timestamp
  and X-ApiCore-Delivery headers, so receivers can reject replays.
- Signing secrets are stored AES-256-GCM-encrypted; older plain rows still
  work.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 02:17:40 +02:00
Nick SzaunigandClaude Opus 5.5 ebeb499a18 fix(backup): keep GUI backup paths under BACKUP_ROOT and limit the schedule
backup:configure could point automatic backups at any writable directory
(e.g. a statically served one) and set a cron of every second; an invalid
expression deleted the running job before failing, silently ending
backups.

- A path entered in the UI is resolved under BACKUP_ROOT (default
  ./backups) and refused if it leaves it; env values stay unrestricted.
- The schedule must be a five-field cron that runs at most hourly.
- reload() validates before replacing the running job.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 02:14:18 +02:00
Nick SzaunigandClaude Opus 5.5 bd592adf66 fix(backup): seal backups and require step-up auth to restore
Backups carried every password hash, encrypted MFA secret and webhook
secret in plain JSON, and an import upserted arbitrary rows - including
users - so anyone holding backup:import, or tricking an admin into
importing a file, could overwrite the admin account with a known password.

- Export (download and scheduled) is AES-256-GCM over the gzipped bundle,
  key from BACKUP_ENCRYPTION_KEY or derived from JWT_ACCESS_SECRET via
  HKDF; header is authenticated, decompression is capped. Scheduled files
  are written 0600 in a 0700 directory; run-now returns only the file name.
- Import requires the current password (and TOTP when MFA is on).
- Only a sealed file restores users, roles, grants, webhooks and audit
  entries; an old unsealed file restores business data only and reports
  what it skipped.
- Audit entries are append-only on import.
- Imported documents: entityType checked against the enum, MIME type
  allowlisted and matched against the file signature, path kept under the
  storage root (no more directory traversal via entityType).
- Permission cache is cleared after an import.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 02:12:43 +02:00
Nick SzaunigandClaude Opus 5.5 a25265b88a fix(sso): bind Microsoft sign-in to the IdP identity, not the email claim
SSO picked the local account by the email/preferred_username claim, which
Entra does not verify. Anyone able to present a matching email (another
tenant with a multi-tenant issuer, or an editable attribute) could sign in
as that user without a password.

- The first SSO login links the account to "<iss>|<oid>" (new unique
  column User.ssoSubject); afterwards only that identity signs in, and a
  matching email from another identity is refused.
- Multi-tenant issuers (common/organizations/consumers) disable SSO.
- Emails are compared case-insensitively and stored lower-case, so
  [email protected] and [email protected] can no longer be two accounts; the migration
  lower-cases existing emails where that does not collide.
- An admin changing a user's email clears the link.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 01:20:49 +02:00
Nick SzaunigandClaude Opus 5.5 c4a11b4704 fix(users): never manage or hand out more than the caller holds
A role with users:update could reset an administrator's password or MFA,
change the admin's email to one it controls, or give a colluding account
the ADMIN role (with users:assign-role) - and so become administrator.

Every management action (update, deactivate, activate, reset password,
reset MFA) and every role assignment now requires the target role's grants
to be a subset of the caller's own, like role editing already did. The
target role is read from its grants, so a deactivated admin still counts
as an admin. Changing another user's email revokes their sessions.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 01:16:37 +02:00
Nick SzaunigandClaude Opus 5.5 e0bd7b2095 fix(auth): accept only access tokens as bearer credentials (MFA bypass)
All JWTs (access, MFA setup, MFA challenge, SSO completion) were signed
with the same secret and the access-token strategy checked only signature
and expiry. The MFA challenge token returned after the password step
therefore worked as a full access token: a password alone was enough to
use the API, skipping the second factor.

Every token now carries a pinned issuer, algorithm (HS256) and a
per-purpose audience; the strategy accepts only the access audience and
rejects any payload carrying a helper-token purpose. Existing access
tokens (no audience) stop working; clients renew them via the refresh
cookie.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 01:14:02 +02:00
Nick SzaunigandClaude Opus 5.5 fac66882fa feat: Ein-/Auswinterung und Winterverluste je Volk
- WinterCheck per colony, winter and season (autumn/spring), every field
  editable; loss causes limited to the COLOSS monitoring categories.
- Summary per winter and apiary: loss rate lost/wintered, marked
  provisional while colonies are not yet checked in spring - an unchecked
  colony never counts as survived.
- A loss marks the colony deceased only on explicit request, in the same
  transaction and audited.
- Permissions winter-checks:read/create/update granted by migration to
  beekeeper and staff roles; winter checks included in backups.
- Page "Überwinterung": next action first, one button per row until the
  colony is wintered, German number format.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 17:32:03 +02:00
Nick SzaunigandClaude Opus 5.5 0d23963f07 feat(export): Bestandsbuch für Tierarzneimittel nach Art. 108 VO (EU) 2019/6
- Treatment records supplier, purchase proof and prescribing vet, and
  keeps the apiary the colony stood at on the treatment day.
- Holder name and address in site settings (never in public output;
  regression test in public.service.spec).
- treatment-log.pdf: one sheet per apiary, identical treatments grouped,
  missing mandatory fields printed as FEHLT, uncertain locations marked.
- Shared PDF table helpers extracted from the record card.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 11:10:07 +02:00
Nick SzaunigandClaude Opus 5.5 f263ec79f2 feat(export): Stockkarte - a colony's complete record as PDF
New report colony-record-card (button "Stockkarte (PDF)" on the colony
page): master data, current queen and queen history, every inspection as
a table row (strength, eggs, open/capped brood, queen seen, queen cells,
gentleness, swarm drive, feed, measures, notes), treatments with batch
number and withdrawal period, feedings, varroa counts and harvests.
Landscape A4, table header repeated on each page. Whole history by
default, one calendar year with ?year=.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 06:27:55 +02:00
Nick SzaunigandClaude Opus 5.5 5cf4e87ca3 feat(automation): suggest follow-up checks after treatments and feedings
Two new rules, "Kontrolle nach Behandlung" and "Kontrolle nach
Fütterung", create a check task when a treatment or feeding is recorded.
The due date counts from the measure itself (end of treatment, else its
date; feeding date) plus the configured number of days, and the task
names the product and date. At most one open check per colony and rule.

Both rules ship switched off and without an interval: the right timing
depends on the product (package leaflet) and season, so the beekeeper
sets it. Every task-creating rule can now carry a "due after N days"
setting in the automation page.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 06:23:13 +02:00
Nick SzaunigandClaude Opus 5.5 888edf7e1d feat(labels): cut marks in the sheet margin for trimming with a paper cutter
Label sheets printed on plain A4 are cut with a lever paper cutter. Each
template can now print small hairline cut marks in the sheet margin: one
at both ends of every cut line (left/right edge of each column, top/bottom
edge of each row), 1 mm away from the label and never closer than 2 mm to
the paper edge, so they print on normal printers and never touch a label.
Labels without a gap share one mark. Marks are drawn on every sheet for
the full grid, since the cutter cuts across the whole sheet.

On by default (new column cropMarks, default true); switch it off in the
template designer for pre-die-cut label sheets.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 06:11:03 +02:00
Nick SzaunigandClaude Opus 5.5 445fb3b94b feat: revocable read-only API keys for machine access
Integrations such as the Wabenhain shop sync no longer need a service
account with a password (and without MFA). An admin creates a key under
Verwaltung > Automatisierung > API-Schlüssel.

- Key format ack_<8>_<43>; only its SHA-256 hash is stored, the key is
  shown exactly once. Optional expiry, "last used" timestamp, revocation
  takes effect immediately; create/revoke are audit-logged without the key.
- A key is not a user: JwtAuthGuard sets request.apiKey, PermissionsGuard
  admits it only to routes whose permission is among its scopes and never
  to user-only routes. Scopes come from a short allowlist (shop-sync:read).
- Managing keys requires the new permission api-keys:manage.
- Keys are excluded from backups on purpose; a restore means issuing a new
  key.

Additive migration (new table).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 01:24:17 +02:00
Nick SzaunigandClaude Opus 5.5 ede86c062a feat: dedicated, versioned shop-sync feed for Wabenhain
GET /api/v1/shop-sync returns exactly the data ApiCore releases to the
shop: harvest facts, article master data and bottlings with their
harvest ids. Every query uses an explicit select, so notes, colonies,
treatments and fields added to the models later never leave ApiCore
unless deliberately added to the allowlist.

- New permission shop-sync:read (granted to ADMIN automatically, to no
  other role): the shop's service account needs nothing else, in
  particular no access to harvest or extraction records with notes.
- contractVersion 1; only breaking changes bump it.
- Always the full set: small volume, and a full comparison heals missed
  runs by itself.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-27 20:31:46 +02:00
Nick SzaunigandClaude Opus 5.5 3c7ff1e0b7 docs: document field ownership between ApiCore and Wabenhain
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-27 20:15:02 +02:00
Nick SzaunigandClaude Opus 5.5 2d4da96ad8 docs: record review findings, verification state and new architecture decisions
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-27 13:01:47 +02:00
Nick SzaunigandClaude Opus 5.5 f9374a135a feat: "Heute" start page with one next step, clear task states and motion levels
Start page (AuDHD-oriented, per the agreed "Heute zuerst" layout):
- One highlighted next step: started work first (easy re-entry after an
  interruption), then overdue, due today, then urgent undated tasks.
  Primary action "Erledigt", secondary "Beginnen", link to the related
  colony/apiary/queen.
- "Außerdem heute": at most five further tasks with a large honey-cell
  checkbox; undated automatic suggestions are included so a queenless
  colony does not sit unseen in the full list.
- Completing is the one authored motion moment: the cell fills, the tick
  draws, the row folds away and the next step arrives with a short focus
  pull. Every completion can be undone from the toast (10 s), restoring
  the previous status.
- Status is shown as icon plus word (never colour alone); tasks created by
  automation carry a "Vorschlag" badge, recurring and inspection tasks
  their own - a suggestion never looks like work already done.
- Friendly empty, loading and error states; key figures moved into a
  compact overview below; overdue tasks are no longer listed twice.

Motion: per-device setting Voll / Dezent / Aus in the profile (default
Voll); the OS reduce-motion setting always lowers Voll to Dezent.
New 44 px "touch" button size for gloves at the apiary; themed text
selection and caret.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-27 13:00:44 +02:00
Nick SzaunigandClaude Opus 5.5 5c2ea7bf1e fix: resend offline-queued entries with an idempotency key
When the server saved an inspection, harvest, treatment, feeding, varroa
measurement or swarm check but the answer was lost (dead zone at the
apiary), the form queued the entry and the sync later created it a second
time - including a second automation task. Each opened form now creates
one Idempotency-Key, sends it with the request and stores it with the
queued entry, so the retry returns the first record. Entries queued before
this change still sync without a key.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-27 05:31:04 +02:00
Nick SzaunigandClaude Opus 5.5 5d11756a87 fix: keep quantities, colony/queen states and tasks consistent under retries
Data-integrity fixes found in a systematic review of ApiCore:

- Bottling booked the honey batch amount from a value read outside the
  transaction and wrote an absolute result back, so two concurrent
  bottlings (double tap, two devices) could both pass the check and one
  deduction was lost. Bookings now use a conditional UPDATE inside the
  transaction; corrections and deletions give amounts back atomically.
- Outgoing stock movements could drive stock below zero; the removal is
  now a conditional decrement that refuses when stock is insufficient.
- Weak, conspicuous and queenless colonies were treated as "not active"
  and could not be moved, split or merged - uniting a queenless colony
  is the textbook merge. Only terminal statuses lock a colony now; merging
  a colony into itself and dissolving twice are rejected.
- Moving a queen to another colony failed on the unique currentQueenId
  because the old colony kept pointing at her. The old colony now releases
  her (and becomes QUEENLESS); a queenless colony receiving a queen becomes
  ACTIVE; dissolved colonies cannot receive a queen.
- Completing a task twice moved completedAt and re-sent the webhook;
  reopening a task kept completedAt. Recurring templates created tasks
  without a due date and caught up missed weeks one stale task at a time.
- Automation rules created a new task and warning on every trigger (every
  stock removal below minimum). Tasks now record their source trigger and
  at most one open task per rule and object exists. The varroa threshold
  (mites per day) is only compared with natural-mite-fall rates, not with
  sample-based counts in another unit.
- New opt-in Idempotency-Key support for record-creating endpoints, so
  the offline queue can retry after a lost response without duplicates.
- Permission catalog sync crashed replicas booting concurrently on a
  fresh database (unique violation); it now uses createMany/skipDuplicates.
- Server-side validation for inspection rating scales (1-5), unique
  colony/harvest lists and net weight <= gross weight.
- Stale e2e expectations updated (permission count, trigger list, real PNG
  fixture for the file-signature check).

Migration is additive only (nullable Task column, new table).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-27 05:27:45 +02:00
Nick SzaunigandClaude Opus 5 786d929df9 feat: expose the remaining fields the edit forms were withholding
Backend Image / build-and-push (push) Successful in 2m11s
Frontend Image / build-and-push (push) Successful in 1m53s
The backend accepted every field of these records already; the forms simply
did not offer them, so from the app they were still frozen.

- Inventar: only the condition could be changed. A mistyped inventory number,
  a wrong purchase or maintenance date, a renamed item - all stuck for the life
  of the record. New edit dialog covering every field; the inline condition
  select stays as a shortcut.
- Produkt: the status was shown as a badge but could not be set anywhere, so a
  product could never be discontinued or brought back.
- Aufgabe: the status could only ever move to DONE through the "Erledigt"
  button. IN_PROGRESS and CANCELLED were unreachable, and an accidentally
  completed task could not be reopened.
- Aufgabenvorlage: priority, assignee and the active flag were missing.
  Deactivating was one-way, since only a deactivate endpoint was wired up -
  the active checkbox now reactivates through PATCH.
- Königinnen-Bewertung: notes and the evaluated colony were missing.

Verified by extracting the field set each form submits and comparing it with
the matching update DTO: every form now covers its DTO. The one gap left is
re-linking a document to a different record, which still has no UI.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-22 23:11:12 +02:00