@7365admin1/layer-common 3.2.2-staging.122 → 3.2.2-staging.124

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 (37) hide show
  1. package/assets/css/primitives.css +39 -0
  2. package/components/EntryPass/QrTemplatePreview.vue +27 -16
  3. package/components/Feedback/Form.vue +26 -12
  4. package/components/HygieneUpdateMoreAction.vue +44 -44
  5. package/components/IncidentReport/affectedEntities.vue +41 -20
  6. package/components/NumberSettingField.vue +9 -2
  7. package/components/Signature.vue +40 -34
  8. package/components/UnitPersonCard.vue +42 -13
  9. package/nuxt.config.ts +6 -1
  10. package/package.json +22 -1
  11. package/plugins/vuetify.ts +37 -1
  12. package/utils/materialSymbols.ts +363 -0
  13. package/.changeset/README.md +0 -8
  14. package/.changeset/camera-wall-gated-endpoint.md +0 -47
  15. package/.changeset/camera-wall-plain-english.md +0 -60
  16. package/.changeset/camera-wall-selection-and-guidance.md +0 -65
  17. package/.changeset/config.json +0 -11
  18. package/.changeset/dark-primary-contrast.md +0 -39
  19. package/.changeset/dashboard-dark-theme-contrast.md +0 -15
  20. package/.changeset/notification-preferences.md +0 -17
  21. package/.changeset/pm-sidebar-grouping-and-list-scaffold.md +0 -27
  22. package/.changeset/service-provider-invitation-actions.md +0 -28
  23. package/.editorconfig +0 -12
  24. package/.github/workflows/main.yml +0 -17
  25. package/.github/workflows/publish-staging.yml +0 -47
  26. package/.github/workflows/publish.yml +0 -39
  27. package/PUBLISHING.md +0 -269
  28. package/components/ScheduleTaskAreaFormDialog.vue +0 -141
  29. package/components/ScheduleTaskAreaUpdateMoreAction.vue +0 -104
  30. package/components/TableWithButton.vue +0 -94
  31. package/test/visitor-socket.test.mjs +0 -36
  32. package/tools/render-harness/README.md +0 -87
  33. package/tools/render-harness/baselines.json +0 -117
  34. package/tools/render-harness/harness-init.ps1 +0 -127
  35. package/tools/render-harness/probe.mjs +0 -229
  36. package/tools/render-harness/render-check.mjs +0 -306
  37. package/tools/render-harness/render-check.selftest.mjs +0 -129
@@ -1,141 +0,0 @@
1
- <template>
2
- <v-dialog
3
- :model-value="modelValue"
4
- @update:model-value="(v) => emit('update:modelValue', v)"
5
- max-width="520"
6
- >
7
- <!-- 520px is the handoff's `--modal-w`, and the card wears `.screen-modal`
8
- for the 16px corner, the `--card` surface and the modal shadow. The
9
- two `v-toolbar`s standing in for the title row and the footer were
10
- fixed grey slabs that stay grey on the dark theme, and the Create
11
- button was a hardcoded `color="black"` fill on a `rounded-0` square. -->
12
- <v-card class="screen-modal">
13
- <template v-if="$slots.header">
14
- <slot
15
- name="header"
16
- :title="
17
- mode === 'edit' ? `Edit ${entityType}` : `Add New ${entityType}`
18
- "
19
- :onClose="onClose"
20
- />
21
- </template>
22
- <v-card-title v-else class="text-capitalize">{{
23
- mode === "edit" ? `Edit ${entityType}` : `Add New ${entityType}`
24
- }}</v-card-title>
25
-
26
- <v-card-text class="screen-modal__body">
27
- <v-form ref="formRef" v-model="valid">
28
- <v-row>
29
- <v-col cols="12">
30
- <InputLabel for="name" :title="label" required />
31
- <!-- Still a `v-text-field`: Submit is gated on `valid`, which
32
- only exists because this `:rules` runs. `.filter-field` gives
33
- it the design's 40px / 10px / 13.5px shape without taking the
34
- validation away. -->
35
- <v-text-field
36
- v-model="nameValue"
37
- class="filter-field"
38
- :rules="[requiredRule]"
39
- />
40
- </v-col>
41
- </v-row>
42
- </v-form>
43
- </v-card-text>
44
-
45
- <div class="screen-modal__footer">
46
- <v-btn
47
- variant="outlined"
48
- class="screen-btn-ghost"
49
- @click="close"
50
- >
51
- Cancel
52
- </v-btn>
53
-
54
- <!-- Kept a `v-btn`: `loading` is the only in-flight signal this form
55
- has, and `AppButton` has no equivalent. -->
56
- <v-btn
57
- variant="flat"
58
- class="screen-btn-primary"
59
- :loading="submitting"
60
- @click="submit"
61
- >
62
- {{ mode === "edit" ? "Save" : "Create" }}
63
- </v-btn>
64
- </div>
65
- </v-card>
66
- </v-dialog>
67
- </template>
68
-
69
- <script setup lang="ts">
70
- const props = defineProps({
71
- modelValue: { type: Boolean, required: true },
72
- mode: { type: String as PropType<"add" | "edit">, default: "add" },
73
- area: { type: Object as PropType<Record<string, any> | null>, default: null },
74
- label: { type: String, default: "Name" },
75
- nameModel: { type: String as PropType<string | null>, default: null },
76
- entityType: { type: String, default: "Area" }, // values: "Area", "Unit"
77
- });
78
-
79
- const emit = defineEmits([
80
- "update:modelValue",
81
- "saved",
82
- "update:nameModel",
83
- ] as const);
84
-
85
- const formRef = ref(null);
86
- const valid = ref(false);
87
- const submitting = ref(false);
88
-
89
- const form = reactive({ name: "", description: "", status: "active" });
90
-
91
- watchEffect(() => {
92
- if (props.area) {
93
- form.name = props.area.name || "";
94
- form.description = props.area.description || "";
95
- form.status = props.area.status || "active";
96
- } else {
97
- form.name = "";
98
- form.description = "";
99
- form.status = "active";
100
- }
101
- });
102
-
103
- const { requiredRule } = useUtils();
104
-
105
- const nameValue = computed({
106
- get: () => {
107
- return props.nameModel !== null ? props.nameModel : form.name;
108
- },
109
- set: (v: string) => {
110
- if (props.nameModel !== null) {
111
- emit("update:nameModel", v);
112
- } else {
113
- form.name = v;
114
- }
115
- },
116
- });
117
-
118
- function close() {
119
- emit("update:modelValue", false);
120
- }
121
-
122
- const onClose = () => close();
123
-
124
- async function submit() {
125
- if (!valid.value) return;
126
- submitting.value = true;
127
- try {
128
- await new Promise((r) => setTimeout(r, 500));
129
- emit("saved", { ...form, _id: props.area?._id || Date.now().toString() });
130
-
131
- form.name = "";
132
- form.description = "";
133
- form.status = "active";
134
-
135
- emit("update:modelValue", false);
136
- } catch (e) {
137
- } finally {
138
- submitting.value = false;
139
- }
140
- }
141
- </script>
@@ -1,104 +0,0 @@
1
- <template>
2
- <!-- The schedule-area copy of `Dialog/UpdateMoreAction`, converted the same
3
- way: two `v-toolbar`s (fixed grey slabs that stay grey on the dark
4
- theme) for the title row and the footer, and a hardcoded `color="black"`
5
- fill on a square 48px tile. -->
6
- <v-card class="screen-modal" width="100%">
7
- <template v-if="$slots.header">
8
- <slot name="header" :title="title" :onClose="onClose" />
9
- </template>
10
- <v-card-title v-else class="text-capitalize">{{ title }}</v-card-title>
11
-
12
- <v-card-text class="screen-modal__body pb-0">
13
- <slot name="content" />
14
- </v-card-text>
15
-
16
- <div class="screen-modal__footer">
17
- <v-btn
18
- variant="outlined"
19
- class="screen-btn-ghost"
20
- @click="emit('close')"
21
- >
22
- Close
23
- </v-btn>
24
-
25
- <v-menu>
26
- <template #activator="{ props }">
27
- <v-btn
28
- variant="flat"
29
- class="screen-btn-primary"
30
- v-bind="props"
31
- >
32
- More actions
33
- </v-btn>
34
- </template>
35
-
36
- <!-- Dropdown shape left as Vuetify draws it - the handoff specifies
37
- the profile dropdown only. `text-red` (Vuetify's fixed #f44336,
38
- the same red on both themes) becomes `text-error`, which is
39
- `--v-theme-error`, i.e. the design's `--err`, matching the
40
- handoff's "red Logout row (err color, err-bg hover)". -->
41
- <v-list class="pa-0 area-more__list">
42
- <v-list-item v-if="canUpdate" @click="emit('edit')">
43
- <v-list-item-title>
44
- {{ editButtonLabel }}
45
- </v-list-item-title>
46
- </v-list-item>
47
-
48
- <v-list-item
49
- v-if="canDelete"
50
- @click="emit('delete')"
51
- class="text-error"
52
- >
53
- <v-list-item-title>
54
- {{ deleteButtonLabel }}
55
- </v-list-item-title>
56
- </v-list-item>
57
- </v-list>
58
- </v-menu>
59
- </div>
60
- </v-card>
61
- </template>
62
-
63
- <script setup lang="ts">
64
- const prop = defineProps({
65
- canUpdate: {
66
- type: Boolean,
67
- default: true,
68
- },
69
- canDelete: {
70
- type: Boolean,
71
- default: true,
72
- },
73
- editButtonLabel: {
74
- type: String,
75
- default: "Edit Area",
76
- },
77
- deleteButtonLabel: {
78
- type: String,
79
- default: "Delete Area",
80
- },
81
- title: {
82
- type: String,
83
- default: "Area Actions",
84
- },
85
- });
86
-
87
- const emit = defineEmits(["close", "manage", "edit", "delete"]);
88
- const {
89
- canUpdate,
90
- editButtonLabel,
91
- deleteButtonLabel,
92
- manageButtonLabel,
93
- title,
94
- } = prop;
95
- </script>
96
-
97
- <style scoped>
98
- /* `text-subtitle-2` was Vuetify's 0.875rem/500; the handoff names no type for
99
- a menu row, so it takes the established cell type. */
100
- .area-more__list :deep(.v-list-item-title) {
101
- font-size: var(--fs-cell);
102
- font-weight: var(--fw-cell);
103
- }
104
- </style>
@@ -1,94 +0,0 @@
1
- <template>
2
- <v-card flat border="sm black" :loading="loading" class="px-0" :ripple="false">
3
- <v-toolbar class="px-5">
4
- <template #prepend>
5
- <span class="text-subtitle-1 font-weight-black">{{ tableTitle }}</span>
6
- </template>
7
- <template #append>
8
- <v-btn color="primary" variant="flat" size="large" density="comfortable" :text="buttonLabel"
9
- class="text-capitalize" />
10
- </template>
11
- </v-toolbar>
12
- <v-card-text class="pa-0 bg-red">
13
- <v-data-table :headers="headers" :items="items" :item-value="itemValue" :items-per-page="itemsPerPage"
14
- fixed-header hide-default-footer hide-default-header @click:row="$emit('row-click', $event)"
15
- style="max-height: calc(100vh - (200px))">
16
- <template v-for="(_, slotName) in $slots" #[slotName]="slotProps">
17
- <slot :name="slotName" v-bind="slotProps" />
18
- </template>
19
- </v-data-table>
20
- </v-card-text>
21
- </v-card>
22
- </template>
23
-
24
- <script setup lang="ts">
25
- const selected = defineModel({
26
- type: Array as PropType<Array<string>>,
27
- default: () => [],
28
- });
29
- const page = defineModel("page", { type: Number, default: 0 });
30
-
31
- const props = defineProps({
32
- headers: {
33
- type: Array as PropType<Array<Record<string, string | number>>>,
34
- required: true,
35
- default: () => [],
36
- },
37
- items: {
38
- type: Array as PropType<Array<Record<string, any>>>,
39
- required: true,
40
- default: () => [],
41
- },
42
-
43
- itemValue: {
44
- type: String,
45
- default: "_id",
46
- },
47
- itemsPerPage: {
48
- type: Number,
49
- default: 20,
50
- },
51
- loading: {
52
- type: Boolean,
53
- required: true,
54
- default: true,
55
- },
56
- tableTitle: {
57
- type: String,
58
- default: "",
59
- required: false
60
- },
61
- buttonLabel: {
62
- type: String,
63
- default: "Add",
64
- required: false
65
- }
66
- });
67
-
68
- const emit = defineEmits(["refresh", "update:pagination", "row-click"]);
69
-
70
- function increment() {
71
- page.value++;
72
- emit("update:pagination", page.value);
73
- }
74
-
75
- function decrement() {
76
- page.value--;
77
- emit("update:pagination", page.value);
78
- }
79
-
80
- function tableRowClickHandler(_: any, row: any) {
81
- const item = props.items[row.index];
82
- emit("row-click", item);
83
- }
84
-
85
- const selectAll = ref(false);
86
-
87
- watch(selectAll, (curr) => {
88
- selected.value.splice(0, selected.value.length);
89
- if (curr) {
90
- const ids = props.items.map((i) => i._id as string);
91
- selected.value.push(...ids);
92
- }
93
- });
94
- </script>
@@ -1,36 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { test } from "node:test";
3
-
4
- import { cameraAlertMessage } from "./.build/useVisitorSocket.mjs";
5
-
6
- /*
7
- * The reported symptom was one camera raising a red toast every 10 seconds on
8
- * an unrelated page. API-core now paces the alert; this side has to make sure
9
- * several cameras failing at once still cannot become several snackbars.
10
- */
11
-
12
- test("nothing is shown when no camera is down", () => {
13
- assert.equal(cameraAlertMessage([]), "");
14
- });
15
-
16
- test("one failing camera shows the server's own message, which names it", () => {
17
- const text = "The entry and exit ANPR camera at Seventh Condominium is not responding.";
18
- assert.equal(cameraAlertMessage([text]), text);
19
- });
20
-
21
- test("several failing cameras collapse into a single line", () => {
22
- const many = ["camera one is down", "camera two is down", "camera three is down"];
23
- const text = cameraAlertMessage(many);
24
- assert.match(text, /^3 cameras at this site are not responding/);
25
- for (const message of many) assert.doesNotMatch(text, new RegExp(message));
26
- });
27
-
28
- test("the collapsed line points at the page that lists them", () => {
29
- assert.match(cameraAlertMessage(["a", "b"]), /Site Settings > Cameras/);
30
- });
31
-
32
- test("no camera message tells the operator to deactivate anything", () => {
33
- for (const messages of [["a camera is down"], ["a", "b"]]) {
34
- assert.doesNotMatch(cameraAlertMessage(messages), /inactive|deactivat/i);
35
- }
36
- });
@@ -1,87 +0,0 @@
1
- # Render harness hardening
2
-
3
- Tooling, not product code. Nothing here ships in `@7365admin1/layer-common` and
4
- nothing here is imported by any component — it exists to stop the theme-review
5
- harness reporting a pass it has not earned.
6
-
7
- The harness itself (vite + Vuetify + a mirror of this package, driven by
8
- Playwright) lives outside the repo, under
9
- `_scratch-theme-review/harness`. These four files attach to it.
10
-
11
- | File | What it does |
12
- |---|---|
13
- | `harness-init.ps1` | Makes a private harness for one worktree: fork directory and dev port both derived from the worktree path, own vite cache, mirror stamped with what was synced. |
14
- | `render-check.mjs` | The run. Refuses to measure an unprovable mirror, then measures each screen at 360/768/1024/1440 × light/dark. |
15
- | `probe.mjs` | The in-page half — what is actually read off the glass. |
16
- | `render-check.selftest.mjs` | Proves every detector fails the scenario it was written for. `node render-check.selftest.mjs`, no server needed. |
17
- | `baselines.json` | Per-screen recorded element and painted-text counts. |
18
-
19
- ## Use
20
-
21
- ```powershell
22
- cd <worktree>
23
- ..\iservice365-layer-common\tools\render-harness\harness-init.ps1 # prints fork + port
24
- Start-Process -WorkingDirectory <fork> cmd.exe -ArgumentList '/c','npx vite --port <port> --strictPort'
25
- cd <fork>; $env:PORT=<port>
26
- node render-check.mjs <label> CameraMain NotificationSettings
27
- ```
28
-
29
- Re-run `harness-init.ps1` after any edit to `assets`, `components`,
30
- `composables`, `constants`, `types` or `utils` — `render-check.mjs` compares the
31
- mirror to the worktree file by file and refuses to run when they disagree.
32
-
33
- ## What each guard is for
34
-
35
- Every one of these was a real false pass on 2026-08-14, not a hypothetical.
36
-
37
- - **Shared mirror.** One `lc/` copy meant two agents silently measured each
38
- other's branch. Isolation is now derived from the worktree, so there is
39
- nothing to remember, and the mirror carries a stamp the run checks.
40
- - **Empty state.** A panel scored 8/8 dark-clean while drawing "No access logs
41
- found". A table was signed off with zero header columns and "No data
42
- available", so the header and cell rules under review had never been drawn. A
43
- screen showing its empty state is a fixture bug and fails.
44
- - **Empty form.** The same bug in a shape nothing else can see — 1112 elements,
45
- nothing blank, and not one control holding a value.
46
- - **Blank screen.** Six elements, zero page errors, and a counter read it as a
47
- pass. Painted text is the honest signal.
48
- - **Element counts.** `MIN_ELS = 20` was at once too low (a cold partial paint
49
- at 32 scored ok where warm is 49) and too high (a drawing screen at 19 was
50
- reported NOT-MOUNTED). Screens legitimately range from 3 to 1100 elements, so
51
- each carries its own baseline and an un-baselined screen is refused rather
52
- than guessed at.
53
- - **Cold compile.** The run waits for the DOM to stop growing instead of a fixed
54
- timeout, warms every screen first, prints the discarded warm-up numbers so the
55
- gap is visible, and re-measures any row well under that screen's own best.
56
- - **Undefined custom properties.** `var(--success)` where the token is `--ok`:
57
- the declaration is invalid, the element inherits, and nothing reports it. Now
58
- caught even when the rule matches nothing on the screen — which is how the
59
- original survived a full 8-shot render.
60
- - **Disabled controls.** WCAG-1.4.3-exempt, so logging them as failures put
61
- fictional entries in an AA ledger. Classified as exempt, and the enabled state
62
- is probed separately so the exemption cannot hide a real failure.
63
- - **Vuetify defaults.** `VAutocomplete` was missing from the harness, and 19
64
- components in this package use one — the run would have invented a defect the
65
- product does not have. The defaults are diffed against `plugins/vuetify.ts`
66
- every run.
67
-
68
- ## Baselines
69
-
70
- `--record` writes the current run's element and painted-text counts:
71
-
72
- ```powershell
73
- node render-check.mjs --record base CameraMain
74
- ```
75
-
76
- Only record from a run whose **screenshots you have opened**. A baseline
77
- recorded from a broken render makes the break permanent. A conversion that
78
- genuinely shrinks a screen below 70% of its baseline will flag — look at the
79
- shot, then re-record.
80
-
81
- `baselines.json` accepts `"allowEmpty": true` per screen for the rare component
82
- whose empty state IS the thing under review.
83
-
84
- ## Rule that does not bend
85
-
86
- Fixtures are harness-only. Package code is never bent to suit the harness. If a
87
- screen will not draw, the fixture is wrong.
@@ -1,117 +0,0 @@
1
- {
2
- "CameraMain": {
3
- "els": 55,
4
- "textChars": 85,
5
- "recordedFrom": "0642279"
6
- },
7
- "NotificationSettings": {
8
- "els": 150,
9
- "textChars": 485,
10
- "recordedFrom": "0642279"
11
- },
12
- "OvernightParkingAvailability": {
13
- "els": 1132,
14
- "textChars": 385,
15
- "recordedFrom": "0642279"
16
- },
17
- "BulletinExpirationChip": {
18
- "els": 3,
19
- "textChars": 2,
20
- "recordedFrom": "0642279"
21
- },
22
- "AccessCardAssignToUnitForm": {
23
- "els": 177,
24
- "textChars": 131,
25
- "recordedFrom": "178ebf0"
26
- },
27
- "AccessCardReplaceForm": {
28
- "els": 68,
29
- "textChars": 83,
30
- "recordedFrom": "178ebf0"
31
- },
32
- "AddEqupmentForm": {
33
- "els": 90,
34
- "textChars": 99,
35
- "recordedFrom": "178ebf0"
36
- },
37
- "BuildingForm": {
38
- "els": 126,
39
- "textChars": 69,
40
- "recordedFrom": "178ebf0"
41
- },
42
- "BulletinBoardForm": {
43
- "els": 172,
44
- "textChars": 165,
45
- "recordedFrom": "178ebf0"
46
- },
47
- "CameraForm": {
48
- "els": 127,
49
- "textChars": 80,
50
- "recordedFrom": "178ebf0"
51
- },
52
- "DocumentForm": {
53
- "els": 52,
54
- "textChars": 57,
55
- "recordedFrom": "178ebf0"
56
- },
57
- "InvitationClientForm": {
58
- "els": 101,
59
- "textChars": 47,
60
- "recordedFrom": "178ebf0"
61
- },
62
- "InvitationForm": {
63
- "els": 86,
64
- "textChars": 43,
65
- "recordedFrom": "178ebf0"
66
- },
67
- "ServiceProviderFormCreate": {
68
- "els": 51,
69
- "textChars": 38,
70
- "recordedFrom": "178ebf0"
71
- },
72
- "AcceptDialog": {
73
- "els": 24,
74
- "textChars": 106,
75
- "recordedFrom": "178ebf0"
76
- },
77
- "Card/DeleteConfirmation": {
78
- "els": 21,
79
- "textChars": 129,
80
- "recordedFrom": "178ebf0"
81
- },
82
- "Dialog/DeleteConfirmation": {
83
- "els": 21,
84
- "textChars": 129,
85
- "recordedFrom": "178ebf0"
86
- },
87
- "HidAccessLogDashboard": {
88
- "els": 172,
89
- "textChars": 437,
90
- "recordedFrom": "178ebf0"
91
- },
92
- "HidIntercomManagement": {
93
- "els": 148,
94
- "textChars": 167,
95
- "recordedFrom": "178ebf0"
96
- },
97
- "HidUserEnrollment": {
98
- "els": 142,
99
- "textChars": 287,
100
- "recordedFrom": "178ebf0"
101
- },
102
- "HidReaderManagement": {
103
- "els": 112,
104
- "textChars": 377,
105
- "recordedFrom": "178ebf0"
106
- },
107
- "HidQrCodeConfiguration": {
108
- "els": 106,
109
- "textChars": 453,
110
- "recordedFrom": "178ebf0"
111
- },
112
- "HidServiceSettingsPanel": {
113
- "els": 18,
114
- "textChars": 164,
115
- "recordedFrom": "178ebf0"
116
- }
117
- }