@7365admin1/layer-common 3.2.2-staging.177 → 3.2.2-staging.178

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/CHANGELOG.md CHANGED
@@ -1,5 +1,314 @@
1
1
  # @iservice365/layer-common
2
2
 
3
+ ## 3.2.6
4
+
5
+ ### Patch Changes
6
+
7
+ - 2a4f61d: 08142026 Release new version
8
+
9
+ ## 3.2.5
10
+
11
+ ### Patch Changes
12
+
13
+ - 1d6b57b: Draw the camera wall from the gated wall endpoint, and show the server's own
14
+ reason on a tile.
15
+
16
+ `CameraWall` fetched its cameras from `GET /api/site-cameras` - the Settings
17
+ panel's paginated CRUD list, which took its site from an optional query
18
+ parameter and checked nothing about the caller. Any signed-in user from any
19
+ organisation could read every camera in the estate through it, `host` and
20
+ `username` included.
21
+
22
+ It now calls `GET /api/site-cameras/site/:siteId/wall`, which already exists,
23
+ which the React Native monitoring app was built against, and which is
24
+ authorised server-side: the caller must be a member of the site, of its owning
25
+ organisation, or work for an organisation actively engaged to serve it. That
26
+ endpoint also returns `type: "ip"` cameras only and each camera's capability
27
+ descriptor, so a wall cannot be handed an ANPR unit and a tile can explain
28
+ itself.
29
+
30
+ Two consequences worth stating:
31
+
32
+ - **The pager is gone.** The wall endpoint returns the site's cameras in one
33
+ answer. Paging a video wall was an artefact of borrowing the CRUD list.
34
+ - **A tile now prefers the server's `unavailableReason`** over the reason it
35
+ used to work out itself. The server knows things the browser cannot - chiefly
36
+ "No recorder is configured for this camera's relay", which is the true answer
37
+ for every camera in the estate until the API host is configured, and which the
38
+ wall used to replace with its own guess. Offline still beats everything, since
39
+ it explains every tile at once.
40
+
41
+ No permission gate was invented here. Each application still gates its own menu
42
+ entry; this component draws what it is given, and the server decides.
43
+
44
+ Two smaller things in the same area:
45
+
46
+ - **A refused wall now says so.** The catch-all message told every failure to
47
+ "check your connection", which sends somebody who simply may not see that
48
+ site off to debug their wifi. A 401/403/404 now reads "You do not have access
49
+ to this site's cameras." The server answers "not yours" and "does not exist"
50
+ identically, so this wording does not distinguish them either.
51
+ - **`middleware/member.ts` is removed.** It read a cookie into an unused
52
+ variable and did nothing else - a file named like a membership gate that was
53
+ not one. No page in any of the eleven web apps referenced it. The real check
54
+ is `plugins/secure-member.client.ts`, driven by `memberOnly` page meta, which
55
+ is untouched.
56
+
57
+ - e3b3cd7: Rewrite the camera wall's detail panel for the people who read it, and stop
58
+ repeating the same sentence four times.
59
+
60
+ The panel under the tiles was written for a developer. On the live wall it
61
+ read:
62
+
63
+ > BBQ AREA
64
+ > The video page loaded, but this browser cannot confirm a picture is arriving.
65
+ > The player belongs to the video service and a browser cannot look inside it.
66
+ > Judge the tile by what you can see, and report a blank or frozen picture.
67
+ > ● Recorder responding Last picture this server received: none yet
68
+ > Firmware and device clock are not available: this recorder is reachable over
69
+ > video only.
70
+
71
+ Every one of those statements is true, and the screen was still wrong: it
72
+ explained cross-origin isolation to a guard, repeated the camera's name the
73
+ tile already showed, put a green light beside the words "none yet", and printed
74
+ the same reason on four capability cards in a row.
75
+
76
+ Changes, all copy and layout — **no behaviour, no capability gating and no
77
+ permission check is touched**:
78
+
79
+ - **The badge reads "Not confirmed"**, not "Not verified". "Not verified"
80
+ describes a check we failed to make; "Not confirmed" describes the picture,
81
+ which is the thing the reader cares about. Same claim, and the wall still
82
+ refuses to say "Live" until the video service reports a decoded frame.
83
+ - **The sentence behind it is now an instruction**: "This page cannot confirm
84
+ the picture is arriving, so check the tile yourself. If it is blank or frozen,
85
+ report the camera." Why the software cannot confirm it is our problem, and it
86
+ has been removed from the product.
87
+ - **The camera's name is no longer a heading of its own.** It is the subject of
88
+ the status line — "BBQ AREA · Recorder responding" — so a reader on a 3x3 wall
89
+ still knows which tile the panel describes, without a second caption under the
90
+ first one.
91
+ - **"Last picture" is drawn only when there has been one.** A green "Recorder
92
+ responding" beside "none yet" read as a contradiction. They were never in
93
+ conflict, but a screen that has to be explained is wrong. The tile's own
94
+ health line has always behaved this way; the panel now matches it.
95
+ - **The firmware-and-device-clock sentence is gone**, from the panel and from
96
+ the tile's tooltip. This screen never showed either value, so explaining their
97
+ absence gave the reader nothing to do. The server still sends
98
+ `detailUnavailableReason`; nothing draws it.
99
+ - **One reason for the group instead of four copies.** When every switched-off
100
+ control is off for the same reason, that reason and its next step are stated
101
+ once above the row and each card keeps only its title and its state. When the
102
+ reasons differ — "the recorder is unreachable" and "this camera cannot move"
103
+ are two problems with two different people to ask — the group note is refused
104
+ and each card carries its own again. New rule 7, in `camera-wall.ts`, with
105
+ four tests.
106
+ - Shorter next-step and no-signal wording, and a subject on the recorder's
107
+ status labels ("Recorder not checked yet" rather than "Health not checked
108
+ yet").
109
+
110
+ Rules 3 and 6 are intact: the server's own sentence is still shown verbatim and
111
+ never rewritten, and the recorder's state is still a separate fact from the
112
+ tile's badge.
113
+
114
+ - a02619c: Mark the selected camera on the wall, and stop telling the reader to raise a
115
+ ticket without saying what for.
116
+
117
+ Two things the owner found on the live wall after the last copy pass. Both are
118
+ copy and presentation only — **no behaviour, no capability gating, no permission
119
+ check and no request is changed.**
120
+
121
+ **1. The camera's name was still printed twice.**
122
+
123
+ The tile said `BBQ AREA` and the panel underneath said it again. The last pass
124
+ merged the panel's heading into its status line, which changed the layout and
125
+ left the repetition exactly where it was.
126
+
127
+ The name was there to answer "which of these nine tiles is this panel about?".
128
+ That is a real question and a name is the wrong answer to it — it makes the
129
+ reader scan the wall for a matching caption. So:
130
+
131
+ - **the selected tile is now visibly selected**: a 3 px accent ring with a scrim
132
+ hairline inside it, so the ring holds against a bright picture as well as a
133
+ dark one, plus the accent underline on the name chip that the toolbar already
134
+ uses to mark a selected tool. Both read at 1x1, 2x2 and 3x3;
135
+ - **the camera's name is removed from the panel entirely**. The panel opens on
136
+ the recorder's state — "● Recorder responding";
137
+ - `aria-current` marks the same tile for anyone not looking at the ring.
138
+
139
+ The highlight had never worked in the most common case, which is why the name
140
+ was load-bearing: it was bound to the raw clicked id, and before anyone clicks —
141
+ or after paging — nothing on the wall was marked while the panel was already
142
+ describing the first tile. It is now bound to the tile the panel actually
143
+ resolves to, so the two can never disagree.
144
+
145
+ **The panel can never be about a tile that is off screen**, so it never needs to
146
+ name one: the focused camera is resolved out of the current PAGE, so paging away
147
+ from a selection re-resolves to the first tile of the page in front of you and
148
+ the ring follows. Paging back restores the original selection. Extracted as
149
+ `focusedCamera()` in `camera-wall.ts` with four tests, including the 5-cameras
150
+ -across-2-pages case.
151
+
152
+ **2. "Ask your Seven365 administrator" was a dead end.**
153
+
154
+ It was the whole of the advice under every switched-off control, and it tells
155
+ the administrator nothing and a property manager only to go and wait. The screen
156
+ never said what the request should ask for.
157
+
158
+ The advice now names the two changes that are actually missing, because they are
159
+ two different jobs for two different people: **the site's network has to let our
160
+ server reach the camera recorder**, and **that recorder then has to be added to
161
+ the server's configuration**. Both are stated in plain words, with who arranges
162
+ them, and with the fact that neither is switchable from this page.
163
+
164
+ No environment variable name appears in any of it — those belong in
165
+ `iservice365-core/docs/camera-integration-config.md`, not on a property
166
+ manager's screen — and there is a test that keeps it that way. The other reason
167
+ codes (`device-http-disabled`, `device-http-unreachable`, `device-http-locked-out`,
168
+ `control-not-enabled`, `no-recorder-configured`, and the unknown-code fallback)
169
+ get the same treatment; the Site Settings ones already said what to do and are
170
+ unchanged.
171
+
172
+ The truthful-status rules are untouched: the wall still refuses to say "Live"
173
+ until the video service reports a decoded frame, and the recorder's state is
174
+ still a separate fact from the tile's badge.
175
+
176
+ - 9fdc87b: A shared CCTV camera wall for the web apps
177
+
178
+ `CameraWall.vue` and `CameraWallTile.vue` draw a site's cameras as a monitoring
179
+ wall, with the layout, tile state and refresh behaviour in `utils/camera-wall.ts`
180
+ so it can be measured by a test rather than only seen.
181
+
182
+ The wall is drawn from the site-scoped camera endpoint, so it shows the cameras
183
+ of the site the operator is actually on and nothing else. Property Management
184
+ and Security both mount the same component instead of each growing their own.
185
+
186
+ Also removes `middleware/member.ts`, a route middleware that read a cookie into
187
+ an unused variable and did nothing else. Nothing in this layer or in either
188
+ consuming app referenced it.
189
+
190
+ - 9fdc87b: Camera wall status made honest, plus zoom, health and paging
191
+
192
+ **A tile that used to say "Live" may now say "Not confirmed", and that is the
193
+ point.** The badge was never measured — it said "Live" because a tile had been
194
+ rendered, not because anything had confirmed the camera was answering. A camera
195
+ that had been down for a week still read as Live. Status is now reported from
196
+ what was actually observed, so a tile only claims Live when the health check
197
+ says so, and says "Not confirmed" when nothing has come back yet.
198
+
199
+ **Tell QA before this ships.** The honest badge reads as a regression if you are
200
+ not expecting it: the wall will look like it has _lost_ status on cameras that
201
+ previously appeared healthy, when in fact those tiles were never measuring
202
+ anything.
203
+
204
+ Also adds zoom on a tile, per-camera health, and paging for sites with more
205
+ cameras than fit a screen.
206
+
207
+ ## 3.2.4
208
+
209
+ ### Patch Changes
210
+
211
+ - ec40e1f: Split the dark theme's `primary` into a foreground colour and a fill, so text
212
+ using it is readable again.
213
+
214
+ Dark `primary` was set to the brand navy `#17506F` so the navigation drawer
215
+ would read as a surface. That is the right value for a fill — a white label on
216
+ it measures 8.70:1 — but Vuetify emits `.text-primary` from the same token, so
217
+ every `class="text-primary"` sentence and every `color="primary"` icon, spinner
218
+ and text button rendered navy on a dark background:
219
+
220
+ | | before | now |
221
+ | ----------------------------------------------- | ------ | ---------- |
222
+ | `primary` as text on the dark page `#0E1319` | 2.14:1 | **6.40:1** |
223
+ | `primary` as text on a dark card `#1B242F` | 1.80:1 | **5.38:1** |
224
+ | `primary` as text on `surface-bright` `#26313E` | 1.52:1 | **4.53:1** |
225
+
226
+ Dark `primary` is now `#5B9BE0`. That is not a new colour: it is the blue the
227
+ camera wall already draws its accents in (`--vms-accent`), so the two dark
228
+ surfaces in this product agree on one blue.
229
+
230
+ The navigation drawer no longer depends on `primary`. Both themes gain a
231
+ `brand-surface` colour — `#042134` light (identical to light `primary`, so
232
+ light mode renders exactly as before) and `#17506F` dark — and
233
+ `Layout/NavigationDrawer.vue` is painted with it. Its white label still reads
234
+ 16.51:1 light and 8.70:1 dark. `floating` was dropped from the drawer so it
235
+ draws its own edge: the fill is 2.14:1 against the dark page, under the 3:1 a
236
+ boundary wants, and the border at `border-opacity` 0.35 is 3.22:1.
237
+
238
+ Where `primary` is still used as a fill, Vuetify derives `on-primary` from it
239
+ and picks black, which reads at 7.21:1. What a light `primary` cannot carry is
240
+ a hardcoded white label, and a handful of app-side sites still set one — see
241
+ the pull request for the list.
242
+
243
+ The light theme is unchanged. The colours moved to `utils/theme.ts` and
244
+ `utils/theme.test.ts` now measures them, so a token cannot be moved for one use
245
+ and quietly broken for the other again.
246
+
247
+ - e3dd56c: The light and dark themes are ours, not Vuetify's factory pair
248
+
249
+ Only a handful of colours were being overridden, so everything else fell through
250
+ to Vuetify's defaults. One click of the light/dark toggle threw the Seven365
251
+ brand away across all 11 web apps and replaced it with stock Material colours.
252
+
253
+ Both themes now define their full palette — surfaces, text, borders, states —
254
+ so the toggle switches between two deliberate looks instead of switching the
255
+ brand off.
256
+
257
+ The light theme is unchanged in appearance. The work is in making it explicit
258
+ rather than inherited, so a Vuetify default can no longer leak through.
259
+
260
+ ## 3.2.3
261
+
262
+ ### Patch Changes
263
+
264
+ - 55160ca: Stop crashing on a null session in comments, feedback and visitor management
265
+
266
+ Several call sites read `currentUser.value._id` (and `.serviceProvider`,
267
+ `.type`, `.givenName`, `.surname`) with no guard. When the session is briefly
268
+ absent — during sign-out, on an expired session, or before the user has
269
+ resolved on first paint — `currentUser.value` is null and the property read
270
+ throws, taking the page down rather than the one action.
271
+
272
+ Those reads are now optional. Purely defensive: where a user exists the
273
+ behaviour is identical, and where one does not the field goes undefined instead
274
+ of throwing.
275
+
276
+ Covers the feedback list and its accept/submit handlers, the visitor
277
+ check-in/check-out path, and comment loading.
278
+
279
+ ## 3.2.2
280
+
281
+ ### Patch Changes
282
+
283
+ - d81dc2e: Camera-fault alerts get their own snackbar instead of riding the visitor one
284
+
285
+ Camera health was being sent through `showTransientMessage`, the same path as
286
+ unregistered-visitor plate alerts. That path returns early when the site has
287
+ the unregistered-visitor snackbar switched off, so **two of the five ANPR sites
288
+ were being told nothing at all when a camera went down** — the setting that
289
+ silenced them has nothing to do with cameras. The old snackbar was also hidden
290
+ whenever the operator was on the Unregistered tab.
291
+
292
+ Camera health is now its own alert, gated only on the permission to view
293
+ visitor data. It cannot be silenced by the unregistered-visitor switch and is
294
+ not hidden on that tab.
295
+
296
+ Several cameras failing at once collapse into a single line — "N cameras at
297
+ this site are not responding … Open Site Settings > Cameras to see which" —
298
+ rather than one snackbar per camera. Faults are keyed by camera id, so a repeat
299
+ for the same camera replaces its line instead of stacking another.
300
+
301
+ When a camera comes back, the operator who was told it was down is told it
302
+ recovered; the alert clears on its own.
303
+
304
+ Reads the optional `event` (`camera-fault` / `camera-recovered`) and `camera`
305
+ fields added to the socket payload in `@7365admin1/core`. Both are optional —
306
+ against an older API-core that sends a bare `message`, this behaves as it does
307
+ today and treats it as a fault.
308
+
309
+ **Expect an alert burst on first release** from the sites that were silent.
310
+ Warn the client before shipping.
311
+
3
312
  ## 3.2.1
4
313
 
5
314
  ### Patch Changes
@@ -448,6 +448,53 @@
448
448
  </v-card>
449
449
  </v-dialog>
450
450
 
451
+ <v-dialog v-model="dialog.returnNfcCard" max-width="450" persistent>
452
+ <v-card>
453
+ <v-toolbar density="compact" color="">
454
+ <v-row no-gutters class="d-flex fill-height justify-space-between align-center px-4">
455
+ <span class="font-weight-bold">Return NFC Card</span>
456
+ <v-btn icon="mdi-close" variant="text" @click="dialog.returnNfcCard = false" />
457
+ </v-row>
458
+ </v-toolbar>
459
+ <v-card-text class="px-4 pb-2">
460
+ <p class="text-body-2 mb-3">
461
+ Please indicate the status of the NFC card before checking out.
462
+ </p>
463
+ <div>
464
+ <InputLabel class="text-capitalize" title="Full Name" />
465
+ <v-text-field :model-value="selectedCheckoutVisitor?.name" density="comfortable" readonly />
466
+ </div>
467
+ <div>
468
+ <InputLabel class="text-capitalize" title="Location" />
469
+ <v-text-field :model-value="formatVisitorLocation(selectedCheckoutVisitor)" density="comfortable"
470
+ readonly />
471
+ </div>
472
+ <div v-for="(card, idx) in nfcCardReturnStatuses" :key="card.cardId" class="mb-4">
473
+ <div class="d-flex align-center ga-2 mb-2">
474
+ <v-icon size="18" icon="mdi-credit-card-outline" color="purple" />
475
+ <v-chip size="small" variant="tonal" color="purple">{{ card.cardNo }}</v-chip>
476
+ </div>
477
+ <v-select v-model="nfcCardReturnStatuses[idx].status" :items="nfcPassStatusOptions" item-title="label"
478
+ item-value="value" density="comfortable" hide-details />
479
+ <v-textarea v-if="card.status === 'Lost' || card.status === 'Damage'"
480
+ v-model="nfcCardReturnStatuses[idx].remarks" label="Remarks (required)" no-resize rows="3" class="mt-2"
481
+ density="compact" />
482
+ </div>
483
+ </v-card-text>
484
+ <v-toolbar class="pa-0" density="compact">
485
+ <v-row no-gutters>
486
+ <v-col cols="6">
487
+ <v-btn variant="text" block @click="dialog.returnNfcCard = false">Close</v-btn>
488
+ </v-col>
489
+ <v-col cols="6">
490
+ <v-btn color="red" variant="flat" height="48" rounded="0" block :loading="loading.checkingOut"
491
+ :disabled="!canConfirmNfcReturnCheckout" @click="handleNfcReturnCheckout">Confirm Checkout</v-btn>
492
+ </v-col>
493
+ </v-row>
494
+ </v-toolbar>
495
+ </v-card>
496
+ </v-dialog>
497
+
451
498
  <v-dialog v-if="enableUnregistered" v-model="dialog.showUnregisteredPlateDialog" max-width="900" persistent>
452
499
  <v-card class="screen-modal">
453
500
  <!-- No heading in the product; see the note on the check-out dialog. -->
@@ -641,7 +688,7 @@ const {
641
688
  } = useVisitor();
642
689
  const { getBySiteId: getEntryPassSettingsBySiteId } =
643
690
  useSiteEntryPassSettings();
644
- const { createVisitorPass, signQr } = useAccessManagement();
691
+ const { createVisitorPass, signQr, visitorCheckout } = useAccessManagement();
645
692
  const { getReaders, issueVisitorQr } = useHidAmico();
646
693
  const { testConnection } = useWebUsb();
647
694
  const {
@@ -731,6 +778,7 @@ const dialog = reactive({
731
778
  vehicleNumberUsersList: false,
732
779
  showNonCheckedOutDialog: false,
733
780
  showUnregisteredPlateDialog: false,
781
+ returnNfcCard: false,
734
782
  });
735
783
 
736
784
  const loading = reactive({
@@ -764,6 +812,30 @@ const deliveryCompanyList = ref<string[]>([]);
764
812
  const matchingNonCheckedOutVisitors = ref<TVisitor[]>([]);
765
813
  const selectedCheckoutVisitorId = ref<string | null>(null);
766
814
  const skipNonCheckedOutNric = ref(false);
815
+
816
+ const nfcCardReturnStatuses = ref<
817
+ { cardId: string; cardNo: string; status: string; remarks: string }[]
818
+ >([]);
819
+ const nfcPassStatusOptions = [
820
+ { label: "Returned", value: "Returned" },
821
+ { label: "Not Returned", value: "In Use" },
822
+ { label: "Damaged", value: "Damage" },
823
+ { label: "Lost", value: "Lost" },
824
+ ];
825
+ const selectedCheckoutVisitor = computed(() =>
826
+ (matchingNonCheckedOutVisitors.value as any[]).find(
827
+ (v) => v?._id === selectedCheckoutVisitorId.value
828
+ )
829
+ );
830
+ const canConfirmNfcReturnCheckout = computed(() => {
831
+ if (nfcCardReturnStatuses.value.length === 0) return false;
832
+ return nfcCardReturnStatuses.value.every(({ status, remarks }) => {
833
+ if (!status || status === "In Use") return false;
834
+ if ((status === "Lost" || status === "Damage") && !remarks.trim())
835
+ return false;
836
+ return true;
837
+ });
838
+ });
767
839
  const nonCheckedOutVisitorHeaders = [
768
840
  { title: "Name/NRIC", key: "name", align: "start", sortable: false },
769
841
  { title: "Type / Company", key: "typeCompany", align: "start", sortable: false },
@@ -1346,19 +1418,43 @@ async function handleSkipNricNonCheckedOut() {
1346
1418
  dialog.showNonCheckedOutDialog = false;
1347
1419
  }
1348
1420
 
1349
- async function handleCheckout(visitorId: string) {
1421
+ function handleCheckout(visitorId: string) {
1350
1422
  if (!visitorId) {
1351
1423
  errorMessage.value = "Invalid visitor ID. Cannot proceed with checkout.";
1352
1424
  return;
1353
1425
  }
1354
1426
 
1427
+ selectedCheckoutVisitorId.value = visitorId;
1428
+
1429
+ const visitor = (matchingNonCheckedOutVisitors.value as any[]).find(
1430
+ (v) => v?._id === visitorId
1431
+ );
1432
+ const nfcCards = ((visitor?.cards as any[]) || []).filter(
1433
+ (card) => card?.type === "NFC"
1434
+ );
1435
+
1436
+ if (nfcCards.length > 0) {
1437
+ nfcCardReturnStatuses.value = nfcCards.map((card: any) => ({
1438
+ cardId: card._id,
1439
+ cardNo: card.cardNo,
1440
+ status: "In Use",
1441
+ remarks: "",
1442
+ }));
1443
+ dialog.returnNfcCard = true;
1444
+ return;
1445
+ }
1446
+
1447
+ proceedNonCheckedOutCheckout(visitorId);
1448
+ }
1449
+
1450
+ async function proceedNonCheckedOutCheckout(visitorId: string) {
1355
1451
  try {
1356
1452
  loading.checkingOut = true;
1357
- selectedCheckoutVisitorId.value = visitorId;
1358
1453
  const res = await updateVisitor(visitorId as string, {
1359
1454
  checkOut: new Date().toISOString(),
1360
1455
  });
1361
1456
  if (res) {
1457
+ dialog.returnNfcCard = false;
1362
1458
  await checkNonCheckedOutVisitorsBeforeSubmit();
1363
1459
  }
1364
1460
  } catch (error: any) {
@@ -1372,6 +1468,33 @@ async function handleCheckout(visitorId: string) {
1372
1468
  }
1373
1469
  }
1374
1470
 
1471
+ async function handleNfcReturnCheckout() {
1472
+ const visitorId = selectedCheckoutVisitorId.value;
1473
+ if (!visitorId) return;
1474
+
1475
+ try {
1476
+ loading.checkingOut = true;
1477
+ const res = await visitorCheckout({
1478
+ cards: nfcCardReturnStatuses.value.map(({ cardId, status, remarks }) => ({
1479
+ cardId,
1480
+ userId: visitorId,
1481
+ status,
1482
+ remarks: remarks ?? "",
1483
+ })),
1484
+ });
1485
+ if (res) {
1486
+ await proceedNonCheckedOutCheckout(visitorId);
1487
+ }
1488
+ } catch (error: any) {
1489
+ const checkoutErrorMessage = error?.response?._data?.message;
1490
+ console.log("[ERROR]", error);
1491
+ errorMessage.value =
1492
+ checkoutErrorMessage || "An error occurred while returning the NFC card.";
1493
+ } finally {
1494
+ loading.checkingOut = false;
1495
+ }
1496
+ }
1497
+
1375
1498
  const debounceFetchCompany = debounce(
1376
1499
  async () => fetchCompanyListRefresh(),
1377
1500
  200
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "3.2.2-staging.177",
5
+ "version": "3.2.2-staging.178",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "//files": "What a consumer extending this layer actually loads. Without this npm ships the whole working tree - the changesets, the CI workflows, the render harness in tools/ and any scratch directory that happened to exist at publish time. Nuxt resolves a layer by directory, so every runtime directory below has to stay listed; adding a new top-level runtime directory means adding it here too.",