kaafil-react-uikit 0.2.0 → 0.3.0

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.
Files changed (35) hide show
  1. package/dist/admin/index.cjs +73 -73
  2. package/dist/admin/index.js +3 -3
  3. package/dist/{chunk-RG4YDSCN.cjs → chunk-BBC5EX5O.cjs} +4 -4
  4. package/dist/{chunk-RG4YDSCN.cjs.map → chunk-BBC5EX5O.cjs.map} +1 -1
  5. package/dist/{chunk-7UFX3Y2U.cjs → chunk-ECCQW5LN.cjs} +5 -2
  6. package/dist/chunk-ECCQW5LN.cjs.map +1 -0
  7. package/dist/{chunk-HTIY33UH.cjs → chunk-FVK7HMQ5.cjs} +21 -21
  8. package/dist/{chunk-HTIY33UH.cjs.map → chunk-FVK7HMQ5.cjs.map} +1 -1
  9. package/dist/{chunk-OZJ74DVQ.cjs → chunk-HACH2NUM.cjs} +3 -3
  10. package/dist/chunk-HACH2NUM.cjs.map +1 -0
  11. package/dist/{chunk-6WV5VYT2.cjs → chunk-MNUCVVIE.cjs} +59 -4
  12. package/dist/chunk-MNUCVVIE.cjs.map +1 -0
  13. package/dist/{chunk-7ISKFGLW.js → chunk-RWLO6IFX.js} +3 -3
  14. package/dist/chunk-RWLO6IFX.js.map +1 -0
  15. package/dist/{chunk-L3QEMEFM.js → chunk-VLP323AD.js} +5 -2
  16. package/dist/chunk-VLP323AD.js.map +1 -0
  17. package/dist/{chunk-RSC2ZJUP.js → chunk-WTTGPWMR.js} +59 -4
  18. package/dist/chunk-WTTGPWMR.js.map +1 -0
  19. package/dist/{chunk-SIAPCJ7G.js → chunk-YTSYRLGS.js} +4 -4
  20. package/dist/{chunk-SIAPCJ7G.js.map → chunk-YTSYRLGS.js.map} +1 -1
  21. package/dist/{chunk-LJJMNZ7B.js → chunk-YU2242AR.js} +3 -3
  22. package/dist/{chunk-LJJMNZ7B.js.map → chunk-YU2242AR.js.map} +1 -1
  23. package/dist/core/index.cjs +56 -56
  24. package/dist/core/index.js +7 -7
  25. package/dist/manager/index.cjs +48 -48
  26. package/dist/manager/index.js +2 -2
  27. package/dist/traveller/index.cjs +4 -4
  28. package/dist/traveller/index.js +2 -2
  29. package/package.json +2 -2
  30. package/dist/chunk-6WV5VYT2.cjs.map +0 -1
  31. package/dist/chunk-7ISKFGLW.js.map +0 -1
  32. package/dist/chunk-7UFX3Y2U.cjs.map +0 -1
  33. package/dist/chunk-L3QEMEFM.js.map +0 -1
  34. package/dist/chunk-OZJ74DVQ.cjs.map +0 -1
  35. package/dist/chunk-RSC2ZJUP.js.map +0 -1
@@ -34,7 +34,7 @@ function useConflicts(tripRef) {
34
34
  return;
35
35
  }
36
36
  if (choice === "keep-current") {
37
- await engine.outbox.drop(opId);
37
+ await engine.discard(opId);
38
38
  return;
39
39
  }
40
40
  const freshRow = op.entity !== void 0 ? engine.snapshot.list(op.tripRef, op.entity.listName).find((row) => row.id === op.entity?.id) : void 0;
@@ -123,5 +123,5 @@ function useNotifications() {
123
123
  exports.useConflicts = useConflicts;
124
124
  exports.useManagerMe = useManagerMe;
125
125
  exports.useNotifications = useNotifications;
126
- //# sourceMappingURL=chunk-OZJ74DVQ.cjs.map
127
- //# sourceMappingURL=chunk-OZJ74DVQ.cjs.map
126
+ //# sourceMappingURL=chunk-HACH2NUM.cjs.map
127
+ //# sourceMappingURL=chunk-HACH2NUM.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/hooks/useConflicts.ts","../src/core/hooks/useManagerMe.ts","../src/core/hooks/useNotifications.ts"],"names":["useContext","OfflineEngineContext","useState","useOfflineEvent","useMemo","useKaafilClient","useEffect"],"mappings":"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","file":"chunk-HACH2NUM.cjs","sourcesContent":["'use client';\n\n// `08-core-hooks.md §2.7`. Every `409 CONFLICT_VERSION` op currently parked\n// and awaiting a human choice for this trip — entity-agnostic on purpose, so\n// the one shared `ConflictResolver` UI covers rooming/seating/checklist/\n// every versioned entity (`07-offline-and-sync.md §6`).\n//\n// VERIFIED SHAPE (`kaafil-js/src/offline/{outbox,reconcile}.ts`, this\n// session): a \"surface\" conflict reaches `Outbox.park()` with only\n// `{ status, code: 'CONFLICT_VERSION', userMessage }` — no row of either\n// side. The fresh server row, however, was already written into\n// `SnapshotStore` by `reconcileConflict()` before the park (its own \"why the\n// entity is re-pulled first\" header) whenever `op.entity` was set and the\n// re-read succeeded. `currentValue` below is read from there — the snapshot\n// — rather than invented on this hook's own read. `queuedValue` is the\n// parked op's own `body`, the intent that was never applied.\n\nimport type { OutboxOp } from 'kaafil-js/client';\nimport { useContext, useMemo, useState } from 'react';\nimport { OfflineEngineContext } from '../internal/contexts';\nimport type { ConflictRecord } from '../types/offline.types';\nimport { useOfflineEvent } from './useOfflineEvent';\n\nexport interface ConflictsResult {\n readonly conflicts: readonly ConflictRecord[];\n readonly resolve: (opId: string, choice: 'keep-queued' | 'keep-current') => Promise<void>;\n}\n\nfunction isVersionConflict(op: OutboxOp): boolean {\n return op.status === 'parked' && op.parkReason?.code === 'CONFLICT_VERSION';\n}\n\nexport function useConflicts(tripRef: string): ConflictsResult {\n // NOT `useOfflineEngine()` (its throwing form) — called from inside a\n // domain hook's unconditional `build()` (`createDomainHook.ts`), including\n // the render(s) before `OfflineShell` has opened an engine. No engine yet\n // means no ops to have conflicted yet — `conflicts: []`, never a crash.\n const engine = useContext(OfflineEngineContext);\n // `tick` itself is never read below beyond being a `useMemo` dependency —\n // `engine.outbox.all()` is a plain synchronous read with no reactive\n // identity of its own, so bumping this on every `sync.error`/`sync.drained`\n // is what makes the memo below re-run on the events that can change what\n // it returns.\n const [tick, forceRefresh] = useState(0);\n\n useOfflineEvent('sync.error', () => forceRefresh((current) => current + 1));\n useOfflineEvent('sync.drained', () => forceRefresh((current) => current + 1));\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: tick is a deliberate side-channel dep, forcing a re-run against the outbox's own non-reactive state on every sync event (see its declaration above).\n const conflictOps = useMemo(\n () =>\n engine === null\n ? []\n : engine.outbox.all().filter((op) => op.tripRef === tripRef && isVersionConflict(op)),\n // eslint-disable-next-line react-hooks/exhaustive-deps -- same reasoning, see the biome-ignore comment above.\n [engine, tripRef, tick],\n );\n\n const conflicts: readonly ConflictRecord[] =\n engine === null\n ? []\n : conflictOps.map((op) => {\n const currentValue =\n op.entity !== undefined\n ? engine.snapshot\n .list(op.tripRef, op.entity.listName)\n .find((row) => row.id === op.entity?.id)\n : undefined;\n return {\n opId: op.id,\n entity: op.entity?.listName ?? op.path,\n queuedValue: op.body,\n currentValue,\n };\n });\n\n async function resolve(opId: string, choice: 'keep-queued' | 'keep-current'): Promise<void> {\n if (engine === null) {\n return;\n }\n const op = engine.outbox.get(opId);\n if (op === undefined) {\n return;\n }\n if (choice === 'keep-current') {\n // `engine.discard()`, NOT `outbox.drop()`. The queue knows nothing about\n // the snapshot, so dropping a CREATE through it leaves the row the\n // manager just chose to discard on screen permanently: it is keyed on an\n // id only this device ever knew, so no pull, no delta and no tombstone\n // can ever remove it. `discard()` takes the optimistic row with the op.\n await engine.discard(opId);\n return;\n }\n // `keep-queued`: re-issue with the fresh version. The fresh row's own\n // `version` field is what `07-offline-and-sync.md §6` calls\n // `details.currentVersion`; reading it off the snapshot row this hook\n // already fetched above is the same value, sourced from the same\n // re-read `reconcileConflict()` already performed.\n const freshRow =\n op.entity !== undefined\n ? engine.snapshot\n .list(op.tripRef, op.entity.listName)\n .find((row) => row.id === op.entity?.id)\n : undefined;\n const freshVersion = freshRow?.version;\n await engine.outbox.reissue(opId, freshVersion !== undefined ? { ifMatch: freshVersion } : {});\n engine.drainer.kick();\n }\n\n return { conflicts, resolve };\n}\n","'use client';\n\n// `families/manager/01-surface.md`'s own \"trip-discovery gap\" note is now\n// stale — verified against the real engine: `GET /manager/me/trips`\n// (`client.managerMe.listTrips()`) resolves a manager's own live trip\n// assignments plus a sensible `defaultTripId`, and now also the manager's\n// own `fullName` (this build's own small engine addition —\n// `manager-me.dto.ts`'s `manager` field). This session's own product\n// decision overrides that spec note for `ManagerNowSection` and any future\n// manager composite that needs \"which trip(s) does THIS manager see\" —\n// never a host-supplied `assignedTrips` prop for those composites (that\n// prop still exists for `KaafilManagerApp`/`MyTripsScreen` themselves, per\n// spec, since a Surface's own trip-list PANE is a separate, larger\n// decision this hook does not revisit).\n//\n// Same deliberately minimal shape as `useManagerToday.ts`: a plain\n// `client.managerMe.listTrips()` call behind `useEffect`/`useState`, no\n// offline/outbox machinery (this is a live read with no mutation of its\n// own to queue), no capability gate (which trips a manager is assigned to\n// is an ingest fact, not a capability — `01-surface.md`'s own \"Gating\"\n// field, cited).\n\nimport type {\n KaafilClient,\n ManagerOwnTrip,\n ManagerOwnTripsResponse,\n OfflineEngine,\n} from 'kaafil-js/client';\nimport { useContext, useEffect, useState } from 'react';\nimport { OfflineEngineContext } from '../internal/contexts';\nimport { useKaafilClient } from './useKaafilClient';\n\n// Re-exported so a composite (`ManagerTripSection.types.ts`, `TripHero.tsx`)\n// can name one trip row's shape without importing `kaafil-js/client`\n// directly — hard rule #1.\nexport type { ManagerOwnTrip };\n\nexport interface ManagerMeLoading {\n readonly status: 'loading';\n}\n\nexport interface ManagerMeError {\n readonly status: 'error';\n}\n\nexport interface ManagerMeReady extends ManagerOwnTripsResponse {\n readonly status: 'ready';\n /** The server timestamp this read landed with (`meta.serverTime`) — never\n * a local clock read (hard rule #6). */\n readonly syncedAt: string;\n}\n\nexport type ManagerMeResult = ManagerMeLoading | ManagerMeError | ManagerMeReady;\n\n/**\n * The last answer this read actually got, per open client — the same\n * module-level, instance-keyed `WeakMap` shape `../internal/ensureTripPulled.ts`\n * already uses, and for the same reason: five composites\n * (`KaafilManagerApp`, `ManagerNowSection`, `ManagerTripSection`,\n * `ManagerChecklistSection`, `ManagerMeSection`) call this hook, and the\n * field surface mounts and unmounts them as the manager moves between tabs.\n *\n * Without this, EVERY tab switch was a fresh network read that the field\n * persona is by definition not guaranteed to be able to make. On a manager's\n * phone with no signal the read rejected, the hook reported `'error'`, and\n * every one of those composites renders nothing at all for that status — so\n * losing the network and then touching a tab emptied the trip screen, with\n * no way back short of a page reload (`architecture/06-offline-first.md`:\n * the field half must serve what it last knew rather than blank).\n *\n * It is not a cache in front of `kaafil-js` (hard rule #3): nothing is\n * reconciled, no cursor is kept, no event is subscribed to, and a live read\n * still fires on every mount so \"re-mount to refresh\" keeps working. It only\n * stops a failed read from erasing an answer the session already has.\n *\n * It dies with the tab, which is the whole reason for the durable lane below.\n */\nconst lastKnown = new WeakMap<KaafilClient, ManagerMeReady>();\n\n/**\n * The key this hook's answer is cached under in the engine's durable read\n * cache (`kaafil-js`'s `engine.reads`, credential-scoped by the engine).\n *\n * The `WeakMap` above survives a tab switch; it does not survive a RELOAD, and\n * a reload is the field surface's normal case rather than an edge one — a\n * manager's phone discards a backgrounded tab on its own. So an offline\n * manager who reloaded got `'error'` from the very first read, and every\n * composite that reads this hook renders nothing for that status: a blank\n * trip list, with their queued writes sitting intact in IndexedDB behind a\n * screen with no way into them. Durable writes the manager could not reach\n * are indistinguishable, from where they are standing, from lost ones.\n *\n * This response cannot live in the snapshot store: its rows carry a\n * `tripRef`, a name and a status but no `id` and no `version`, so there is\n * nothing for a version-guarded merge to guard (`kaafil-js/src/offline/\n * reads.ts` explains the split at length). It is a whole answer or nothing,\n * which is exactly the promise `engine.reads` makes.\n */\nconst READ_CACHE_KEY = 'manager.me.trips';\n\n/** What is actually persisted — the response plus the `meta.serverTime` it\n * landed with. The store keeps `syncedAt` itself, so only the body goes in. */\ntype CachedManagerMe = ManagerOwnTripsResponse;\n\n/**\n * `client.managerMe.listTrips()` behind a plain fetch-on-mount effect —\n * this read takes no parameter (it resolves entirely off the authenticated\n * principal, `manager-me.routes.ts`'s own header, cited), so it never\n * re-fetches on a prop change the way `useManagerToday(tripRef)` does.\n * A caller wanting \"refresh now\" re-mounts this hook's owning component.\n */\nexport function useManagerMe(): ManagerMeResult {\n const client = useKaafilClient();\n // The raw context, not `useOfflineEngine()`'s throwing form: this hook is\n // read by desk composites too, and a desk mount has no `OfflineShell` and\n // therefore no engine. With none, everything below degrades to exactly the\n // in-memory behaviour this hook had before — which is right for a surface\n // that is online by definition.\n const engine = useContext(OfflineEngineContext);\n\n // Seeded from what is already known, in order: this session's own answer,\n // then the durable one from a previous session. Both beat a skeleton, and\n // the durable one is the only one that exists after a reload.\n const [result, setResult] = useState<ManagerMeResult>(\n () => lastKnown.get(client) ?? readCached(engine) ?? { status: 'loading' },\n );\n\n useEffect(() => {\n let cancelled = false;\n setResult(lastKnown.get(client) ?? readCached(engine) ?? { status: 'loading' });\n client.managerMe\n .listTrips()\n .then((response) => {\n const { meta, ...data } = response;\n const ready: ManagerMeReady = { status: 'ready', ...data, syncedAt: meta.serverTime };\n // Recorded before the cancellation check: a response that landed is\n // true regardless of whether THIS component is still mounted to\n // render it, and the next mount should not have to re-ask.\n lastKnown.set(client, ready);\n // Fire-and-forget, and deliberately not awaited before the state is\n // set: painting the fresh answer must not wait on a disk write, and a\n // failed write costs the NEXT reload's first paint rather than this\n // one's correctness. `meta.serverTime`, never a device clock — the\n // store has no setter that would accept one (hard rule #6).\n void engine?.reads.set(READ_CACHE_KEY, data, meta.serverTime).catch(() => {\n // A full or unavailable store is not this read's problem to report.\n // The live answer above is already rendered and already correct.\n });\n if (!cancelled) {\n setResult(ready);\n }\n })\n .catch(() => {\n if (cancelled) {\n return;\n }\n // `'error'` ONLY when NOTHING is known — not this session, not the\n // last one. A read that failed after one succeeded means the device\n // is offline or the engine is unreachable, not that the manager\n // stopped being assigned to their trips; reporting `'error'` there is\n // what blanked the field surface, and after a reload it blanked it\n // from the very first read. `syncedAt` on the held value already\n // tells a caller how old the reading is.\n setResult(lastKnown.get(client) ?? readCached(engine) ?? { status: 'error' });\n });\n return () => {\n cancelled = true;\n };\n }, [client, engine]);\n\n return result;\n}\n\n/**\n * The last answer a PREVIOUS session got, if this surface has a durable store\n * at all. Returns `undefined` rather than an `'error'` so every call site can\n * fall through to the next-best source in one expression.\n */\nfunction readCached(engine: OfflineEngine | null): ManagerMeReady | undefined {\n const cached = engine?.reads.get<CachedManagerMe>(READ_CACHE_KEY);\n if (cached === undefined) {\n return undefined;\n }\n return { status: 'ready', ...cached.value, syncedAt: cached.syncedAt };\n}\n","'use client';\n\n// `08-core-hooks.md §3` table, row `useNotifications()`. Primary SDK surface\n// (verified, `kaafil-js/src/resources/notifications.ts`):\n// `notifications.list` (`managerAuth`-only), `notifications.markRead`\n// (likewise `managerAuth`-only). NOT trip-scoped — the only hook in this\n// table taking no `tripRef`. `managerAuth` is the ONLY scheme either\n// operation accepts, so this hook is structurally unreachable from the\n// `admin`/`traveller` families. `list` takes an optional `?since=` delta\n// cursor passed straight through, never a UIKit-invented filter language.\n//\n// Deliberately the same minimal shape as `useManagerToday.ts`/\n// `useManagerMe.ts`: a plain fetch-on-mount effect, no offline/outbox\n// machinery. `families/manager/11-offline-sync-and-notifications.md\n// §NotificationsFeed`'s own \"write: markRead enqueues through the outbox\n// with a fixed idempotency key\" contract is that COMPOSITE's job once it\n// is built — this hook exists now only so `ManagerNowSection`'s header\n// bell can show a real (honestly zero, until something writes a\n// notification) unread count, which needs `list()` alone. `markRead` is\n// still exposed as a direct SDK call (never mints its own idempotency key,\n// hard rule #4 — mark-read is naturally idempotent server-side per the\n// SDK's own doc comment, so no key is needed here at all) so an early\n// caller isn't blocked, but a future `NotificationsFeed` composite should\n// route its OWN write through the outbox instead of reusing this hook's\n// `markRead` as-is.\n//\n// The wire's `items` array is a discriminated union (a live row, or a\n// `{ _tombstone: true; id; version; deletedAt }` delete marker) — this hook\n// filters tombstones out of `notifications` (a plain unread-count/list\n// consumer has no use for a deleted row) rather than exposing the union to\n// every caller.\n\nimport type { NotificationsResource } from 'kaafil-js/client';\nimport { useEffect, useState } from 'react';\nimport { useKaafilClient } from './useKaafilClient';\n\n// `notifications.ts`'s resource module exports no named response type of its\n// own (unlike `manager-me.ts`'s `ManagerOwnTripsResponse`) — derived off the\n// resource's own method signature instead, `useFiles.ts`/`useExpenses.ts`'s\n// established precedent for this exact situation.\ntype NotificationListResponse = Awaited<ReturnType<NotificationsResource['list']>>;\ntype NotificationListItem = NotificationListResponse['items'][number];\n\n/** The live-row half of `NotificationListItem`'s union — a tombstone never\n * reaches a caller of this hook (see this file's own header). */\nexport type ManagerNotification = Exclude<NotificationListItem, { _tombstone: true }>;\n\nexport interface NotificationsLoading {\n readonly status: 'loading';\n}\n\nexport interface NotificationsError {\n readonly status: 'error';\n}\n\nexport interface NotificationsReady {\n readonly status: 'ready';\n readonly notifications: readonly ManagerNotification[];\n /** `notifications.filter((n) => n.readAt === null).length` — the wire\n * carries no numeric unread field of its own (verified against\n * `NotificationListResponse`), so this is a real derivation off real\n * rows, never a fabricated count. */\n readonly unreadCount: number;\n readonly markRead: (id: string) => Promise<void>;\n /** The server timestamp this read landed with (`meta.serverTime`) —\n * never a local clock read (hard rule #6). */\n readonly syncedAt: string;\n}\n\nexport type NotificationsResult = NotificationsLoading | NotificationsError | NotificationsReady;\n\nfunction isTombstone(\n item: NotificationListItem,\n): item is Extract<NotificationListItem, { _tombstone: true }> {\n return '_tombstone' in item && item._tombstone === true;\n}\n\nexport function useNotifications(): NotificationsResult {\n const client = useKaafilClient();\n const [result, setResult] = useState<NotificationsResult>({ status: 'loading' });\n\n useEffect(() => {\n let cancelled = false;\n setResult({ status: 'loading' });\n client.notifications\n .list()\n .then((response) => {\n if (cancelled) {\n return;\n }\n const notifications = response.items.filter(\n (item): item is ManagerNotification => !isTombstone(item),\n );\n setResult({\n status: 'ready',\n notifications,\n unreadCount: notifications.filter((n) => n.readAt === null).length,\n markRead: async (id: string) => {\n await client.notifications.markRead({ id });\n },\n syncedAt: response.meta.serverTime,\n });\n })\n .catch(() => {\n if (!cancelled) {\n setResult({ status: 'error' });\n }\n });\n return () => {\n cancelled = true;\n };\n }, [client]);\n\n return result;\n}\n"]}
@@ -177,6 +177,14 @@ function createDomainHook(moduleKey, build) {
177
177
  }
178
178
 
179
179
  // src/core/hooks/useChecklists.ts
180
+ function appendPendingRows(fromAggregate, fromSnapshot) {
181
+ if (fromSnapshot.length === 0) {
182
+ return fromAggregate;
183
+ }
184
+ const known = new Set(fromAggregate.map((row) => row.id));
185
+ const pending = fromSnapshot.filter((row) => !known.has(row.id));
186
+ return pending.length === 0 ? fromAggregate : [...fromAggregate, ...pending];
187
+ }
180
188
  var useChecklistsInternal = createDomainHook("checklists", (tripRef) => {
181
189
  const client$1 = chunkUALMCHNW_cjs.useKaafilClient();
182
190
  const engine = react.useContext(chunkUALMCHNW_cjs.OfflineEngineContext);
@@ -250,7 +258,54 @@ var useChecklistsInternal = createDomainHook("checklists", (tripRef) => {
250
258
  method: client.SyncOpMethod.Post,
251
259
  path: `/api/v1/trips/${tripRef}/checklist/items`,
252
260
  operationId: "addChecklistItem",
253
- body: input
261
+ body: input,
262
+ // ── THE OPTIMISTIC CREATE ──────────────────────────────────────
263
+ // Without this the item a manager adds out of signal does not
264
+ // appear until it syncs. It was queued, durable and landed on
265
+ // reconnect the whole time — and from where the manager is
266
+ // standing, work that does not show up is work that was lost, so
267
+ // they add it again.
268
+ //
269
+ // `creates` makes the ENGINE mint the row's temporary id (this
270
+ // hook never invents identifiers) and swap it for the server's
271
+ // canonical row the moment the write lands, in one
272
+ // `snapshot.updated`, so the item is never rendered twice.
273
+ creates: true,
274
+ entity: { listName: "checklist" },
275
+ // Shaped as a real `ChecklistItemDeltaRow` so the board renders it
276
+ // through the same path as every other row rather than a special
277
+ // "pending" branch. `id` is a placeholder the engine replaces with
278
+ // the id it mints; `version: 0` cannot beat any server row in the
279
+ // snapshot's version-guarded merge.
280
+ //
281
+ // The write addresses a section by its `sectionKey`, but the board
282
+ // groups rows by `sectionId`, so the id is resolved from the sections
283
+ // this hook already holds. Adding to a section that does not exist
284
+ // yet resolves to `''` and the row sits ungrouped until the write
285
+ // lands — correct rather than clever: inventing a section id would
286
+ // put the item under a heading that does not exist.
287
+ optimistic: {
288
+ id: "pending",
289
+ version: 0,
290
+ sectionId: aggregate.sections.find((section) => section.key === input.sectionKey)?.id ?? "",
291
+ key: input.key ?? null,
292
+ title: input.title,
293
+ subLine: null,
294
+ status: "OPEN",
295
+ // `=== true`, never `?? false` (hard rule #5): an omitted
296
+ // `mandatory` is the SERVER's default to apply, not this hook's to
297
+ // guess. The optimistic row shows it unstarred for the moment it
298
+ // takes the write to land, and the canonical row replaces it.
299
+ isMandatory: input.mandatory === true,
300
+ gate: null,
301
+ dayOffset: null,
302
+ sortOrder: Number.MAX_SAFE_INTEGER,
303
+ completedByManagerId: null,
304
+ performedByKind: null,
305
+ performedById: null,
306
+ completedAt: null,
307
+ updatedAt: null
308
+ }
254
309
  }),
255
310
  // ── THE DESK LANE ─────────────────────────────────────────────────
256
311
  // All five checklist writes take the admin triple, and a desk console
@@ -392,7 +447,7 @@ var useChecklistsInternal = createDomainHook("checklists", (tripRef) => {
392
447
  (input) => client$1.checklists.agencyTemplates.publish(input),
393
448
  [client$1]
394
449
  );
395
- const items = syncedAt === void 0 ? aggregate.items : snapshotItems;
450
+ const items = syncedAt === void 0 ? appendPendingRows(aggregate.items, snapshotItems) : snapshotItems;
396
451
  return {
397
452
  items,
398
453
  syncedAt,
@@ -2207,5 +2262,5 @@ exports.useSeating = useSeating;
2207
2262
  exports.useSnapshotList = useSnapshotList;
2208
2263
  exports.useTravellerProfile = useTravellerProfile;
2209
2264
  exports.useVendors = useVendors;
2210
- //# sourceMappingURL=chunk-6WV5VYT2.cjs.map
2211
- //# sourceMappingURL=chunk-6WV5VYT2.cjs.map
2265
+ //# sourceMappingURL=chunk-MNUCVVIE.cjs.map
2266
+ //# sourceMappingURL=chunk-MNUCVVIE.cjs.map