feat: T4 wave 11 – bottling form with article values and confirmation, Telegram in the app
Backend Image / build-and-push (push) Successful in 4m27s
Frontend Image / build-and-push (push) Successful in 9m34s
Manifests / validate (push) Successful in 19s

- Bottling form: jar size and best-before from the product with source,
  differs-hints before saving, prefill status, water-content confirmation
  naming the measured value, idempotency key renewed on body change;
  product form gets shelf life (months) and whole-gram jar weight.
- Telegram frontend: link card with step-up dialog, t.me-only deep link,
  status polling, unlink; system Telegram card with receiver/sender state
  and confirmed retry; StepUpDialog shared with the backup import.
- Reauth calls never refresh-and-retry on 401 (no double lockout count);
  German texts for expired session and 429; service worker never caches
  Telegram, notification settings and system responses.
- Telegram backend round 2: shared link removal on every disconnect path,
  lease released on shutdown, connection errors not counted as poison.
- ARCHITECTURE.md §2, §6 (Annahmen 56–58), §10, §11.12, §11.13; TASKS.md T4.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-15 01:21:08 +02:00
1 parent fab7f7e12f
commit 9760ce154f
73 files changed
+3633 -269

No files matched your search

@@ -74,6 +74,7 @@ class FakeTelegramSender {
}
class FakeTelegramUpdates {
releaseLease = jest.fn<Promise<void>, []>().mockResolvedValue(undefined);
pollOnce = jest
.fn<Promise<string>, [AbortSignal]>()
.mockResolvedValue('no-lease');
@@ -350,6 +351,15 @@ describe('WorkerRuntime', () => {
expect(logError).toHaveBeenCalled();
});
it('hands the lease back at shutdown, after the loops stopped', async () => {
await bootstrap();
telegramUpdates.releaseLease.mockClear();
await runtime.onModuleDestroy();
expect(telegramUpdates.releaseLease).toHaveBeenCalledTimes(1);
});
it('aborts the running poll at shutdown', async () => {
await bootstrap();
const [signal] = telegramUpdates.pollOnce.mock.calls[0];
@@ -188,6 +188,9 @@ export class WorkerRuntime implements OnApplicationBootstrap, OnModuleDestroy {
this.listener = undefined;
await listener?.stop();
await Promise.all([...this.loops.values()].map((loop) => loop.stop()));
// Also covers a stop during the 30 s wait: the next receiver need not
// wait for the lease to run out.
await this.telegramUpdates.releaseLease();
}
/**