feat: platform backbone and field capture complete (checkpoint)
Platform backbone (plan 2026-10-13-plattform-rueckgrat), all tasks reviewed: - webhooks with delivery table, retries and stable delivery id - calendar handlers; a finished task removes its Outlook event - tasks, automations, inspections, varroa, treatments, feedings, harvests, colonies, wintering, stock and bottling record domain events in the same transaction; legacy post-commit calls removed - row locks through one helper (lockRowForUpdate, lockRowsForShare, lockRowsForUpdate) - job framework with JobRun, cli exit codes, cleanup, task templates, daily evaluation, hourly app-backup and the rekey job - system API and page "Verwaltung > System" with retry - @nestjs/schedule removed, guard tests against in-process schedulers Field capture T3 (plan 2026-10-13-t3-erfassung-am-stand), all tasks reviewed: - inspection photo offline without metadata - bulk capture for treatments, feedings and varroa (backend + frontend) - offline stand mode with service worker, offline pages and packs - offline session, cold start and logout on shared devices Docs: ARCHITECTURE.md assumptions and sections updated, TASKS.md. Gates: backend lint, typecheck, 1770 unit, 420 e2e, build, no schema drift; frontend lint, typecheck, 913 tests, build. Not rolled out. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
c01a38835a
commit
cdd30cf3ee
288 files changed
+39165
-2853
No files matched your search
@@ -34,11 +34,11 @@ function setOnLine(value: boolean) {
|
||||
}
|
||||
|
||||
/** The band as the layout places it: above the page content wrapper. */
|
||||
function BandOnPage() {
|
||||
function BandOnPage({ syncEnabled }: { syncEnabled?: boolean }) {
|
||||
const pageContentRef = useRef<HTMLDivElement>(null);
|
||||
return (
|
||||
<>
|
||||
<OfflineBand userId="user-1" pageContentRef={pageContentRef} />
|
||||
<OfflineBand userId="user-1" pageContentRef={pageContentRef} syncEnabled={syncEnabled} />
|
||||
<div ref={pageContentRef} tabIndex={-1} data-testid="page-content">
|
||||
Seiteninhalt
|
||||
</div>
|
||||
@@ -46,11 +46,11 @@ function BandOnPage() {
|
||||
);
|
||||
}
|
||||
|
||||
function renderBand() {
|
||||
function renderBand(props: { syncEnabled?: boolean } = {}) {
|
||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
return render(
|
||||
<QueryClientProvider client={client}>
|
||||
<BandOnPage />
|
||||
<BandOnPage {...props} />
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
}
|
||||
@@ -260,4 +260,39 @@ describe("OfflineBand", () => {
|
||||
|
||||
await waitFor(() => expect(syncPendingEntries).toHaveBeenCalledWith("user-1"));
|
||||
});
|
||||
|
||||
describe("in an offline session (no access token)", () => {
|
||||
it("neither opens the sync gate nor sends, even when the connection is back", async () => {
|
||||
setOnLine(true);
|
||||
vi.mocked(summarizeQueue).mockResolvedValue({ own: 2, failed: 0, foreign: 0 });
|
||||
|
||||
renderBand({ syncEnabled: false });
|
||||
await screen.findByText("Offline – 2 Einträge warten");
|
||||
act(() => {
|
||||
window.dispatchEvent(new Event("online"));
|
||||
});
|
||||
|
||||
expect(resumeSync).not.toHaveBeenCalled();
|
||||
expect(syncPendingEntries).not.toHaveBeenCalled();
|
||||
expect(screen.queryByRole("button", { name: /Jetzt senden/ })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("opens the gate and sends as soon as the session is online again", async () => {
|
||||
setOnLine(true);
|
||||
vi.mocked(summarizeQueue).mockResolvedValue({ own: 1, failed: 0, foreign: 0 });
|
||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
const view = (syncEnabled: boolean) => (
|
||||
<QueryClientProvider client={client}>
|
||||
<BandOnPage syncEnabled={syncEnabled} />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
const { rerender } = render(view(false));
|
||||
await screen.findByText("Offline – 1 Eintrag wartet");
|
||||
|
||||
rerender(view(true));
|
||||
|
||||
await waitFor(() => expect(syncPendingEntries).toHaveBeenCalledWith("user-1"));
|
||||
expect(resumeSync).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user