@propriety/court-calendar 1.0.172 → 1.0.173

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -480,7 +480,7 @@ Publishing is automated via GitHub Actions (`.github/workflows/publish.yml`).
480
480
  ## Known limitations
481
481
 
482
482
  - **Port 8000 only:** The backend API enforces CORS that only allows requests from `localhost:8000`. Applies to the dev server and any locally running consumer.
483
- - **Cache is not invalidated on external changes:** If another user modifies a court date, the change won't appear until the local cache expires (1 minute for dates, 2 minutes for cases).
483
+ - **Cache is not invalidated on external changes:** If another user *modifies* a court date, the change won't appear until the local cache expires (1 minute for dates, 2 minutes for cases). A *deletion* does propagate: the poll drops any in-window date the server stops returning, and a save answered with 404 removes the row immediately — which matters because polling is suspended while a modal is open.
484
484
  - **No pagination for court dates:** `getAllDates` fetches every court date in a single request.
485
485
  - **Single API key:** No built-in token refresh or rotation.
486
486
  - **No error UI for API failures:** Network errors are logged to the console but not surfaced to the user (except for bootstrap errors, which show a brief snackbar).
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Tests for how useCourtDates reconciles a date that no longer exists server-side.
3
+ *
4
+ * On 2026-08-24 court date 499 was deleted at 13:41. At 15:31 a user still saw it in the
5
+ * table view, clicked save on it twice, and got HTTP 404 both times. The calendar had polled
6
+ * for it about twenty-two times in between and every response correctly omitted the row.
7
+ *
8
+ * The reason it survived: the merge was a union — `[...fresh, ...prev.filter(notInFresh)]`.
9
+ * A deletion *is* an absence, so that expression cannot represent one; each poll re-added the
10
+ * row it had just been told about. The union was there for a reason (loadRange merges
11
+ * out-of-window months into the same array and a plain replacement would wipe them), which is
12
+ * why both directions are pinned here.
13
+ */
14
+ export {};
@@ -1,20 +1,4 @@
1
- /**
2
- * A cross-cutting sink for API failures, so a request the server rejected always reaches the
3
- * screen instead of only the console.
4
- *
5
- * Every helper in this folder reports failure by returning `false`/`null` and writing a
6
- * `console.error` — which is why a delete the server refused looked identical to one that
7
- * worked. Rather than change each helper's signature (and every call site, and every
8
- * `.then(ok => …)` that depends on the boolean), each failing branch now *also* publishes an
9
- * `ApiFailure` here. `ApiFailureReporter` subscribes on mount and renders whatever arrives.
10
- *
11
- * A module-level bus rather than React context because these fire from plain async functions
12
- * that have no access to a provider — the same reason crash reporters are built this way.
13
- *
14
- * NOTE: nothing published here may contain the API key. The report is written to be pasted
15
- * into a chat or ticket, so it carries the request *shape* (method, URL, status, response
16
- * body) but never a credential.
17
- */
1
+ import { DateType } from '../../types';
18
2
  export interface ApiFailure {
19
3
  /** What the user was trying to do, in their words: "Delete court date". */
20
4
  operation: string;
@@ -29,6 +13,15 @@ export interface ApiFailure {
29
13
  /** Raw response body (or the thrown error's message), truncated. For the tech team. */
30
14
  body?: string;
31
15
  at: Date;
16
+ /**
17
+ * Which record the request was about, in machine-readable form. `subject` is the same thing
18
+ * for a human to read; this is what lets a subscriber *act* on the failure — a 404 on a save
19
+ * means the row is gone server-side and should leave the screen.
20
+ */
21
+ record?: {
22
+ id: number;
23
+ dateType?: DateType;
24
+ };
32
25
  }
33
26
  type Listener = (failure: ApiFailure) => void;
34
27
  /** Subscribe to API failures. Returns an unsubscribe function suitable for a `useEffect`. */
@@ -36,7 +29,10 @@ export declare function onApiFailure(listener: Listener): () => void;
36
29
  /** Publish a failure to every subscriber. A throwing listener must not break the others. */
37
30
  export declare function publishApiFailure(failure: ApiFailure): void;
38
31
  /** Log and publish a non-2xx response. */
39
- export declare function reportHttpFailure(operation: string, method: string, url: string, res: Response, subject?: string): Promise<void>;
32
+ export declare function reportHttpFailure(operation: string, method: string, url: string, res: Response, subject?: string, record?: {
33
+ id: number;
34
+ dateType?: DateType;
35
+ }): Promise<void>;
40
36
  /**
41
37
  * Log and publish a request that never completed.
42
38
  *
@@ -8,7 +8,8 @@ import { CourtDate } from '../types';
8
8
  * restoring from cache.
9
9
  *
10
10
  * Optimistic updates are applied immediately to both React state and the cache;
11
- * chair-assignment changes are rolled back if the API call fails.
11
+ * chair-assignment changes are rolled back if the API call fails. A date the server
12
+ * reports as gone — absent from a refetch, or answering a save with 404 — is removed.
12
13
  *
13
14
  * @param apiKey API key forwarded to court-date helpers.
14
15
  * @param activeUser Current user ID, forwarded to `updateCourtDate`.