domma-cms 1.6.55 → 1.6.57

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 (113) hide show
  1. package/admin/css/calculator.css +1 -1
  2. package/admin/js/lib/analytics-shape.js +1 -1
  3. package/admin/js/lib/calculator-engine.js +1 -1
  4. package/admin/js/lib/calculator.js +22 -10
  5. package/admin/js/lib/form-editor-arrange.js +1 -1
  6. package/admin/js/lib/forms-arrange.js +1 -1
  7. package/admin/js/lib/notes-arrange.js +1 -1
  8. package/admin/js/lib/search-settings-arrange.js +1 -1
  9. package/admin/js/lib/seo-arrange.js +1 -0
  10. package/admin/js/lib/todo-arrange.js +1 -1
  11. package/admin/js/lib/tool-kit.js +1 -1
  12. package/admin/js/lib/tool-recovery.js +4 -4
  13. package/admin/js/templates/analytics.html +26 -14
  14. package/admin/js/templates/contacts.html +26 -5
  15. package/admin/js/templates/form-editor.html +2 -0
  16. package/admin/js/templates/notes.html +11 -8
  17. package/admin/js/templates/search.html +9 -3
  18. package/admin/js/templates/seo.html +23 -17
  19. package/admin/js/templates/todo.html +18 -14
  20. package/admin/js/views/analytics.js +10 -10
  21. package/admin/js/views/contacts.js +47 -47
  22. package/admin/js/views/form-editor.js +7 -7
  23. package/admin/js/views/form-submissions.js +2 -2
  24. package/admin/js/views/forms.js +5 -5
  25. package/admin/js/views/notes.js +6 -6
  26. package/admin/js/views/search.js +2 -2
  27. package/admin/js/views/seo.js +43 -43
  28. package/admin/js/views/todo.js +8 -8
  29. package/package.json +1 -1
  30. package/plugins/_lib/admin/mail/compose-window.js +4 -1
  31. package/plugins/_lib/admin/mail/mail.css +1 -1
  32. package/plugins/_lib/admin/mail/reader-kit.js +364 -0
  33. package/plugins/_lib/admin/mail/reader-view.js +1009 -161
  34. package/plugins/_lib/admin/mail/when-closed.js +33 -3
  35. package/plugins/_lib/mail/readRoutes.js +48 -10
  36. package/plugins/_lib/mail/unreadBadge.js +4 -1
  37. package/plugins/blog/CLAUDE.md +14 -0
  38. package/plugins/blog/admin/css/index.css +5 -0
  39. package/plugins/blog/admin/lib/drafts.js +95 -0
  40. package/plugins/blog/admin/templates/blog.html +1 -1
  41. package/plugins/blog/admin/templates/categories.html +1 -1
  42. package/plugins/blog/admin/templates/comments.html +1 -1
  43. package/plugins/blog/admin/templates/post-editor.html +5 -1
  44. package/plugins/blog/admin/views/blog.js +44 -19
  45. package/plugins/blog/admin/views/categories.js +138 -20
  46. package/plugins/blog/admin/views/comments.js +32 -23
  47. package/plugins/blog/admin/views/kit.js +232 -22
  48. package/plugins/blog/admin/views/post-editor.js +348 -178
  49. package/plugins/blog/admin/views/settings.js +17 -3
  50. package/plugins/blog/plugin.js +1 -1
  51. package/plugins/blog/plugin.json +7 -7
  52. package/plugins/blog/plugin.public.js +1 -1
  53. package/plugins/blog/tests/drafts.test.js +50 -0
  54. package/plugins/booking/CLAUDE.md +13 -0
  55. package/plugins/booking/admin/lib/drafts.js +139 -0
  56. package/plugins/booking/admin/lib/kit.js +246 -25
  57. package/plugins/booking/admin/views/booking.js +11 -2
  58. package/plugins/booking/admin/views/bookings.js +7 -2
  59. package/plugins/booking/admin/views/diary.js +13 -6
  60. package/plugins/booking/admin/views/panels.js +85 -12
  61. package/plugins/booking/admin/views/people.js +41 -2
  62. package/plugins/booking/admin/views/services.js +45 -3
  63. package/plugins/booking/admin/views/settings.js +22 -2
  64. package/plugins/booking/plugin.json +2 -2
  65. package/plugins/booking/tests/admin-drafts.test.js +67 -0
  66. package/plugins/feedback/CLAUDE.md +12 -0
  67. package/plugins/feedback/admin/css/index.css +5 -0
  68. package/plugins/feedback/admin/lib/drafts.js +145 -0
  69. package/plugins/feedback/admin/lib/kit.js +161 -5
  70. package/plugins/feedback/admin/lib/settings.js +46 -28
  71. package/plugins/feedback/admin/views/feedback.js +1 -1
  72. package/plugins/feedback/admin/views/inbox.js +110 -46
  73. package/plugins/feedback/admin/views/send.js +174 -83
  74. package/plugins/feedback/plugin.json +3 -3
  75. package/plugins/feedback/tests/drafts.test.js +93 -0
  76. package/plugins/free-tier.lock.json +75 -63
  77. package/plugins/security/CLAUDE.md +9 -0
  78. package/plugins/security/admin/lib/screen-state.js +120 -0
  79. package/plugins/security/admin/templates/security.html +15 -10
  80. package/plugins/security/admin/views/security.js +270 -132
  81. package/plugins/security/plugin.json +2 -2
  82. package/plugins/security/tests/screen-state.test.js +69 -0
  83. package/plugins/shopping-cart/CLAUDE.md +13 -0
  84. package/plugins/shopping-cart/admin/css/index.css +21 -5
  85. package/plugins/shopping-cart/admin/lib/drafts.js +174 -0
  86. package/plugins/shopping-cart/admin/lib/kit.js +136 -28
  87. package/plugins/shopping-cart/admin/views/orders.js +117 -36
  88. package/plugins/shopping-cart/admin/views/overview.js +38 -8
  89. package/plugins/shopping-cart/admin/views/products.js +317 -68
  90. package/plugins/shopping-cart/admin/views/sales.js +21 -3
  91. package/plugins/shopping-cart/admin/views/settings.js +62 -3
  92. package/plugins/shopping-cart/admin/views/shop.js +18 -3
  93. package/plugins/shopping-cart/plugin.json +3 -3
  94. package/plugins/shopping-cart/tests/accounts.test.js +11 -0
  95. package/plugins/shopping-cart/tests/drafts.test.js +118 -0
  96. package/plugins/support/CLAUDE.md +14 -0
  97. package/plugins/support/admin/css/index.css +10 -0
  98. package/plugins/support/admin/lib/drafts.js +162 -0
  99. package/plugins/support/admin/lib/kit.js +156 -16
  100. package/plugins/support/admin/lib/settings.js +52 -29
  101. package/plugins/support/admin/views/desk.js +114 -33
  102. package/plugins/support/admin/views/inbox.js +85 -26
  103. package/plugins/support/admin/views/support.js +1 -1
  104. package/plugins/support/plugin.json +3 -3
  105. package/plugins/support/tests/drafts.test.js +92 -0
  106. package/public/js/site.js +1 -1
  107. package/server/routes/api/analytics.js +5 -1
  108. package/server/routes/api/contacts.js +42 -0
  109. package/server/routes/api/forms.js +8 -0
  110. package/server/routes/api/seo.js +4 -1
  111. package/server/routes/api/todo.js +50 -10
  112. package/server/server.js +1 -1
  113. package/server/templates/page.html +1 -1
@@ -1,24 +1,25 @@
1
1
  {
2
2
  "note": "Written by `make sync-free-tier` in dcms-marketplace. Do not edit these plugins here - edit them there and sync.",
3
- "source": "dcms-marketplace@0525295",
3
+ "source": "dcms-marketplace@72b2ff0",
4
4
  "plugins": {
5
5
  "blog": {
6
- "version": "1.9.7",
6
+ "version": "1.10.0",
7
7
  "files": {
8
- "CLAUDE.md": "641e27358f95bf4415e9cfc507645d45f4c6a15f1ca9ebef753068f43ec867ff",
9
- "admin/css/index.css": "6142a6b756e1218157842528830b1e84f592f0b84b7d5ce6449db421b6f4b039",
10
- "admin/templates/blog.html": "a6054c1febe764bb1edf5c9e466d44a9080d9c83ba5486437b7a8244acf5ba54",
11
- "admin/templates/categories.html": "ab7d6fc2fedd48876c585bc216a1532b3bdd78c946dee976c1171bfe4664feb6",
12
- "admin/templates/comments.html": "095ff03bd5e1ed9ac75edf1ceb9fbe7b2907a1e35b261672da51e00742c2814a",
13
- "admin/templates/post-editor.html": "f826089f040a13188087721eaa5324707b2f7d491bad41610682242d5e86cec6",
8
+ "CLAUDE.md": "67bea3889545180a0f385a42f8392453f89c8568d59303f3afb23804edbcf078",
9
+ "admin/css/index.css": "8d6dfd2bfadf0bbe8efbd52ef9b397ca9d36ca699c80e260aec11f677c77a248",
10
+ "admin/lib/drafts.js": "60446f6bf93b790f07041fbb95dac30b12cd0ebc3caa71d15f2a7dc16c661b0d",
11
+ "admin/templates/blog.html": "1632dc30cbdd39dad3939a6b137c52fcc76fbd3ea52bd5f5574d8a4d915aac0b",
12
+ "admin/templates/categories.html": "d87d20704fb661d50276a4a1447d350656aee808bcf36af73e33bc4e2ccca464",
13
+ "admin/templates/comments.html": "70893fe44a60de42c7977a91edc87c86cbc065d68c2f80d0b5fbe7fb83176ebc",
14
+ "admin/templates/post-editor.html": "24212ae8aa3de55443637a65af83dfe9eeba577090cc3110c09ddc87aaec8c31",
14
15
  "admin/templates/settings.html": "e0730cd57435e592034328a699198d5e1326084767156bfc2289852f5f1a45af",
15
- "admin/views/blog.js": "e260e1b7f9c4b22350d9c4f2170693fabb3d14e0ac9cab1d0e075630b5706fc4",
16
- "admin/views/categories.js": "75140feb2b382ba0ab4a52c30007845bf576883c99577fc0965783adb8808f6b",
17
- "admin/views/comments.js": "064dad6316dd4aea6bcd414418405894f0e5d282cd5a333c878a6cdbf27b0f2e",
18
- "admin/views/kit.js": "91292d63a96c1d1e9fdc0c8f931d0f31e07fa37d06f06e0fc45b308863e126aa",
19
- "admin/views/post-editor.js": "60da0f8b2d508afd8dbd7671e3b408cf72d81c46b94100cf495439e18d0d63ee",
16
+ "admin/views/blog.js": "959a2e9e22ba3e9ab19932cdf34002ec6bc592ac83a65febf08f5626e39765ba",
17
+ "admin/views/categories.js": "92946bba9740e5f3ce6770bf283d5db5f061353cf7afa10ecf1dac38ead7d837",
18
+ "admin/views/comments.js": "e122a8a4120203535bd8345263cb4d511e8e4750e0b68b83fe0e722c3b096244",
19
+ "admin/views/kit.js": "a7d705de235bd7f125e5ff0607134863c1e67112f3b5876ed97dbbdded202dbc",
20
+ "admin/views/post-editor.js": "a522f6f9692180e207b64b932d1ed999578dc6b14a2011dc9a74c4ab7f489d12",
20
21
  "admin/views/settings-cog.js": "307efc93835863dd8511f8fb17a224cf45a77532c90b854a86d2cd1d4e5f9f18",
21
- "admin/views/settings.js": "2b7f775e37e034dee4ae3dfadea5e446c2e526bed8a066ef3ca2e777146ada23",
22
+ "admin/views/settings.js": "256729c7fc2c7cc30541b19afd2efb335a8ac2cc8bdf15202a360c9b7c07c1e4",
22
23
  "blocks/blog-card-compact.css": "eb8e8ae9a6461c362bc021ea0396c6b4481047b6b36a50eeb59e47375e49be75",
23
24
  "blocks/blog-card-compact.html": "7e1e31923808e6108d57d34309d3442b5d93b4aa4396f0e471e870249934ab8f",
24
25
  "blocks/blog-card-row.css": "f5f0249c51a40cc71ff8d054bfa887353badc79730b0f323c76ac32f7d893b44",
@@ -50,9 +51,9 @@
50
51
  "lib/page.js": "02ef5b0e5aadfe5a8a5de4b7471518b24193b1dad2bda23bea06c6cc1e4743e7",
51
52
  "lib/render.js": "0e97c6a950bc962c8d8ea4b2f21d06cb73fbcdc738d8942d6935d01309a29b1d",
52
53
  "lib/samples.js": "e8cdb77ce22e43937707f6854d3a5cb722437dc584e22ac7b24480bef686cf85",
53
- "plugin.js": "4a2b1cd87a7d91889d3a7d87bc1ada2909a5127e95fc2e5448fd814b16ee83bb",
54
- "plugin.json": "b5c2ab90d78c3cb8c4cfe7b72f66e3c5237fbbb48bb640b4490b3afbd1d45b10",
55
- "plugin.public.js": "2aac0a56584e7aec30000f7718256d7ae4cf889b821f44d4848b435ed031bd39",
54
+ "plugin.js": "74b791100e3382fe62faf78e135df07f1ebafb4b52269890dac357ded7e54832",
55
+ "plugin.json": "37dda4ee698f50eb444422fbd7454e91db195ce6b635b19b7946aa4f0b14565e",
56
+ "plugin.public.js": "081ca0ef3e91fa5712392b332f19f17f45bd9ef741f1372c8a583c28bfa7f535",
56
57
  "public/blog.css": "fd733e6b624190da1e70dc1b6613e2376efd7f7570a28fab311d81006d03d960",
57
58
  "public/blog.js": "d01c72a9834dcc7c5241923371f2b164ac30d20ac9f1a5c5dc5b71fa3cb09f08",
58
59
  "public/samples/aurora.svg": "feb29e2b46465913fc29de9eda221b00b1541f1fa3ea7120f0c1d1cd11a9a4f6",
@@ -68,6 +69,7 @@
68
69
  "tests/access.test.js": "65b760910117c3a44195543acdcff91074f39b4e5567911a2bd8676204152cb2",
69
70
  "tests/cache.test.js": "f823d9ff65c5b5594a8e3d1d3b598cc3524f10885ddf34017cf0a9364878a98f",
70
71
  "tests/comment-notices.test.js": "34026cc9a2a4746cf87bc41df8be849fadaa43091a72ac2aa1d06c1f437bf618",
72
+ "tests/drafts.test.js": "7d4c86f13b61a99bab3ef17448f4c633b401c1cd79a41d346fe59288042c437f",
71
73
  "tests/layouts.test.js": "554cff8194bd650cfb5f2a83c1f51c25f427c07f7dbfdc2ad9ad69bb38fca6a0",
72
74
  "tests/nav-link.test.js": "d36e33ad7483abca27ae6e2098ad2f75bafa18d91f3beba70372ebe6b9b88994",
73
75
  "tests/notices.test.js": "2b02aa84e780dc4c82e6c503fc8fe17c64a63bee0709627866ef97c99cb4e131",
@@ -75,22 +77,23 @@
75
77
  }
76
78
  },
77
79
  "booking": {
78
- "version": "1.6.4",
80
+ "version": "1.7.0",
79
81
  "files": {
80
- "CLAUDE.md": "76debc74ef2fcf4e9a78bb9256ade4ffbc179541876f6e58d00a8cc0e0e216e1",
82
+ "CLAUDE.md": "afca62bf08d8dca02402c784d33e3882a480f09c8a11e60f54ad20d1424f748d",
81
83
  "admin/css/index.css": "4f7731921c0c5038cb1566a6ab2d43ffc35bf9b161e58f87a0cff423e81b47fa",
82
84
  "admin/lib/details.js": "8b37536bfa9ccfcc5265cc957ba8266d3f5f777133e7c0946ae49540f1c80e3c",
83
85
  "admin/lib/diary-layout.js": "d0e36674ff454e5217b02c3ef3b820e4dece97a62df618a6052016dfeefab1c9",
86
+ "admin/lib/drafts.js": "90d6c8403ef7c6a01569ad925939ce7634ab0853f7e3097d522c1e850636d6b1",
84
87
  "admin/lib/extension-sections.js": "0588ad47660e511b0dd8077bb9c3999c775d97597333a75e87ee12d915bbf00b",
85
- "admin/lib/kit.js": "b69b02172d76ef80879da3f003cc1e687c6149626c9e58af51729c2e4a3ff959",
88
+ "admin/lib/kit.js": "403e792195e95770f6304bafab14ff13295c2bcc68cccda27e019a6739d0e888",
86
89
  "admin/templates/booking.html": "20b9a3bfe3e5d5bbee613f29be99551b12790fb38f80d119585360cc38e9ec57",
87
- "admin/views/booking.js": "4eddf6c0b60dbbba4c6e35352df247a3d34b66ee1d43c51fe2d53f894707348f",
88
- "admin/views/bookings.js": "bf17db82ed4d2f5c29ac924dd60dc0b84c8bd9c8ce32da6cbb011527699a56a6",
89
- "admin/views/diary.js": "6cdbf81106a5b8ec2ed905f2e30df8ee626db2db51241bc9b41499ea2ed6ad27",
90
- "admin/views/panels.js": "1d269d90553a5482fd6ebd18180cb736011770f0204d5c1306948abcb4b676b2",
91
- "admin/views/people.js": "11e9bb811378a288bb44e879d84251b8e9aaeee68490d64f4db44d8727c38376",
92
- "admin/views/services.js": "122326d222ed16dc8ec903fef6f62f6a7638c31d73c4612b7a8c2ced38358068",
93
- "admin/views/settings.js": "958a4f99df843fbcb884adeab4f768ebb2338fff5c361e8bbad3ddc90ef992e0",
90
+ "admin/views/booking.js": "939bf77016880ee091a9ee6cd6633cac0014801426b4c5bff9863d63aecd87a6",
91
+ "admin/views/bookings.js": "bbe1068448e78134a5102ad9d7af16892f75a4449f150e92e649e66e7232e56b",
92
+ "admin/views/diary.js": "bea962e57fc3be6ed07f5c6588889fffb106bc251193c0c48743f1f2442a6eef",
93
+ "admin/views/panels.js": "313bf09704e9fbed379c6f7a4ffb000c53460b9747311310482514c0c6d680c1",
94
+ "admin/views/people.js": "55cc6d07b98ed305d577772b921eb8d09b564b099cb8d35441c1b39631e4764a",
95
+ "admin/views/services.js": "32a2539391d06ec576c0af6d7aef1bbf71e22d6335c3654acb80491e56f19f41",
96
+ "admin/views/settings.js": "7af543fa0e2772025c436b60b9a49282baa45f8af42e6d202096b9fcbc6336c2",
94
97
  "collections/booking-bookables.json": "9f1f305eceac3d880413d5477a631e48c1fed7ff389c434f34651f4082ca1a1c",
95
98
  "collections/booking-bookings.json": "fa0b15cc81b4a2114734a0bc8adc4b4d25049d16cf62549b83634cdbeb543b52",
96
99
  "collections/booking-services.json": "cecb500a9215a0eba894e6b4c9ae78f5fbeb209830d07d47050c56c75143f66a",
@@ -102,7 +105,7 @@
102
105
  "lib/mail.js": "cad8f409df62d6e42f0f4024ec364bbe018a62b842fa3f7c38786fd4e7f92408",
103
106
  "lib/store.js": "7aa7967e4694e5a3b85378f60302e5a47394b1d90c7dd86e09578156f1c13e03",
104
107
  "plugin.js": "4a303855a5a7f746e07e45a937dfeac8984dc9254c2f9b1a60b6f6306912ed84",
105
- "plugin.json": "8d90802c7c8c3681903b69200b19af88d326242bc33742bb8c61777c8b99775d",
108
+ "plugin.json": "d05342466f1f48d18e2a1bf8452a2609de69f9c470d523985bd24ef518eb2ab7",
106
109
  "plugin.public.js": "4d5b4e8ad6943182ee3b6c6e6f6762a2d3f676d8d7ee363fcb07bd47d3d6532e",
107
110
  "public/booking.css": "8b88989fdd04e50efc1688a2f32fe2e422299d7d13b81bb8fce0cc162fa99df6",
108
111
  "public/booking.js": "7dfc3d205696967013886567f67a867443c2e370bac29019d69911d695fdbade",
@@ -112,22 +115,24 @@
112
115
  "public/lib/time.js": "48e13a23e3d7a4916e2097e8f87c03a87d37b71c4ef037a5a16143c6a4185c41",
113
116
  "templates/booking.html": "a4995de46dfdab7e692b56f21347f29c76320cf3b0477889dc3c5d14e0b6dcd7",
114
117
  "tests/admin-diary.test.js": "6bd117af798e748234c94e4e66339ff1ec3bba9633790a2a5fd291f64c30e484",
118
+ "tests/admin-drafts.test.js": "c9e857b2326965b3dbb34e344282d874348f84b5cd9338ced2ca2c7a07713a25",
115
119
  "tests/api.test.js": "efb3767f7efde08c11c8edcb5753889773b421188798f725aff0e4e533860aff",
116
120
  "tests/public.test.js": "0b7b626d60df5a3d73d7c0077b73f59b62a9fded98ce84a8d93cd32cd0333159",
117
121
  "tests/pure.test.js": "8ba0c8f76b4c9efcc422f39ff29509612544d81f760025a4b9a5594af14e7bcf"
118
122
  }
119
123
  },
120
124
  "feedback": {
121
- "version": "1.1.8",
125
+ "version": "1.2.0",
122
126
  "files": {
123
- "CLAUDE.md": "8666d4ef8d8620f0fc20f544a4c1915cddc21370cbc27a7f854ebfe41f4cfe1d",
124
- "admin/css/index.css": "c8f827ad62386134e87bc05bd2c8e24f41184402b0a5a17e7250d749240c34c0",
125
- "admin/lib/kit.js": "502d2d090e8aaeda03034864b4e3cc0d9bbf7e8fba29b6e4916a8a758c0cd54d",
126
- "admin/lib/settings.js": "6e3fe9103fcbf7acb52211698ca408fac067e061100e580a9a3011cd76bdfba3",
127
+ "CLAUDE.md": "a65ccffeb80bd9df2814167f989cd37da790d7218599d86c6b6cfb8491425549",
128
+ "admin/css/index.css": "0703057c66370a242a41f6666836b7e3976ed3fda21774b2b23e770098b4ebd5",
129
+ "admin/lib/drafts.js": "3ccf8c255e72d62edf31692bc1bd464cea43ae9f135de23ce2caaedd0502eea2",
130
+ "admin/lib/kit.js": "f4ec5b7e9147f7ca9f2b775d0f973390015920b7af9eebe0735509a631cac064",
131
+ "admin/lib/settings.js": "973c1934c2241336d6ec25d98f9fd96077faf28ccab553f2c8507f404b4f8975",
127
132
  "admin/templates/feedback.html": "a366dce78f6c3e84a7cb0ac63eba53e1980a4eb44154496a0536f5214c5bb36f",
128
- "admin/views/feedback.js": "5aed85f09ef8af762d425912ca7fb4299ec129a21822863edf431af4f1a12e64",
129
- "admin/views/inbox.js": "20cc1bd16df59c775f0e2d398f4179d0e30683a3a76a942e250f59f7cbdce98a",
130
- "admin/views/send.js": "104290b8f58f7e8d75d86de0b3f5496bd38546b6bfb20ddff45e153e61f7d061",
133
+ "admin/views/feedback.js": "bcb86953504804fb8e6937dfe132dd342ece73daafa9987074e304bac5d76261",
134
+ "admin/views/inbox.js": "55553e6bc9c2c1c593cbcdc828aeb112daa91ec01d4ae2df8702c8e696c91803",
135
+ "admin/views/send.js": "17efa5dac111f28677be9ced3710fc83eef5b7d81781aec0156b18f4bf9d22b0",
131
136
  "collections/feedback-reports.json": "a072291b52463f7785a77f5f77d36b8238ba8f89cfc2672c6642eccdb3144486",
132
137
  "config.js": "29d9999d1028c2c11931f67fb9abdf35132430b7cf58c161681e734c0b45143a",
133
138
  "docs/guide.md": "a2a14f834da9fcb59222249d925ad3a387a1c7e7ec03aad84e0f512f936cc670",
@@ -137,8 +142,9 @@
137
142
  "lib/sender.js": "58194d1ab562e55e7ac6a29aed6b5ec8316ec6932b40b44034bb347e8f799f79",
138
143
  "lib/shape.js": "47ccbbb95598023f8de03dd0f557205159375fd2bb61ee799a19c186c45bc4d9",
139
144
  "plugin.js": "dd9cf5066e8491184a782112ed9463a211b37c2dbd93caf832edd65c2bb0e2a4",
140
- "plugin.json": "9712d3eba3d81a0a8f5f85dfa168ea158d14b6ef9c0de6416314938c1b41f6ed",
145
+ "plugin.json": "dd49ddfb207ee8b1da37e8789494ed7b36b21f1a6f41e74f6a7c5bae18f21ddf",
141
146
  "tests/api.test.js": "67336ed4a034e21ead5876becefa1de2fa2229b06689385471516df92e198d33",
147
+ "tests/drafts.test.js": "b9bac3c1c7f3841229bdfe5294bfda78064f0f2d170b6db1ba39e7666531ac1f",
142
148
  "tests/settings.test.js": "3a50ac89a9d114c8466fc043a66f0f1bf373e7a7ad6dba45a995468c3332b374",
143
149
  "tests/shape.test.js": "2e2e5e17ce059fbaa4dad8170ae45382d6c7e74bacf3b301814bb81a60ba98ba"
144
150
  }
@@ -155,9 +161,9 @@
155
161
  }
156
162
  },
157
163
  "security": {
158
- "version": "1.1.3",
164
+ "version": "1.2.0",
159
165
  "files": {
160
- "CLAUDE.md": "29507f4460053b1a16a1902d47c007213d83ad3b497e86398ccaab92d07d3d51",
166
+ "CLAUDE.md": "c4a8cedeff2af492871d2b9af446c6b73792881f4e428e189516105508bc07e5",
161
167
  "admin/lib/blocks.js": "5ef346b7de751518f8beb9212e3b89b270ae236560ae3d8a1f40d129c50918ff",
162
168
  "admin/lib/health.js": "3b233a3d29b2d69c333321602c13233b8221cfe1114aa03903739d9ae8ad65d5",
163
169
  "admin/lib/ip.js": "cf33be4fa2e2cad37bafd12ebec17f293891a88de402caad1bf9c9a57ce87680",
@@ -165,35 +171,38 @@
165
171
  "admin/lib/policy.js": "a4aedfd28f992e0e7759bbdac6344681eebe617c27849e91fa4405f6190f8462",
166
172
  "admin/lib/probes.js": "d56318903cb645a39f56e7762eddb7411941a72e1f446c1f9d79e35157f1d31b",
167
173
  "admin/lib/resets.js": "3724a2aec9f3c18ada96948aa6ce375ae3505bb7623a2c556e2e2670c5ca343c",
174
+ "admin/lib/screen-state.js": "70acb23f5e57b59e546979ada08cbc50b77a84b82325e0aac2a6b4eb9a7c64a3",
168
175
  "admin/lib/settings.js": "b319953914f3a6f029ba991b36fe6fcd521956ea099578be42505f30e6c32faf",
169
- "admin/templates/security.html": "df5798497ca1fd5ed200ce0ff921a055168ac7720f38b2db1bebf2c0efe71f04",
170
- "admin/views/security.js": "89c8dcaea89a0b8999798ecb84e9b41e5a08ceefa404aff529b02a6f28874361",
176
+ "admin/templates/security.html": "97b7d2684a3c65132c3a34a8ba44bb563656ee860f5ea9e152f0d244b00c5816",
177
+ "admin/views/security.js": "f9e2a465ff04cc9d68b13df3405c35d2ba30458339907fd593dbcd2f012c7ff4",
171
178
  "bin/unlock.js": "5fcc3c0ef3ba3c424a366aa62896f51abad578183dfde283aa838a86549ca785",
172
179
  "config.js": "e6a00f7816637687d6a68ebbac65a71489246a926c28523c2a78d859e75b7ae0",
173
180
  "docs/guide.md": "a27d5303f9146057fd79c2093d7476ada1a5ee4fd14d34df7f94ad8555d88ca0",
174
181
  "plugin.js": "9d3a2e8b56dd637407c8abd93252dda9f67042231bc79d35771f766a62022c14",
175
- "plugin.json": "c3457c117c66a8434318e51e13f0569d5b0f3f5e8500c9864a95c9c91049ce15",
182
+ "plugin.json": "5cf525d76b1f043a475268f8d37622bdb53120e0c34a1068930d7fcff88ea686",
176
183
  "server/common-passwords.NOTICE": "3b148a49a569bfa24feaa7879db602da8f0607fa61ab6c52b7a62391fc7c009a",
177
184
  "server/common-passwords.txt": "68782d6a4a19a4768d5f15dd66bd534e7a33055cc755411e33f16d18c50fdcce",
178
185
  "server/facts.js": "25538a8d1cfe012688fceb8ec0d0dd7b43ac9e168430723d6fb731183701fb46",
179
186
  "server/state.js": "1319b322e94a4aec176948990b2d674b28dfece761eef4d5685df474037bfc4a",
187
+ "tests/screen-state.test.js": "5349273cf7cfe526a05f929115a6e4e66287e6fb05b252ea859e182e88e3925e",
180
188
  "tests/security.test.js": "f0cb70dcb28bc8b3de8d3b39064616296382c5b0f97bfd615ee8e24fe3b031bc"
181
189
  }
182
190
  },
183
191
  "shopping-cart": {
184
- "version": "1.6.3",
192
+ "version": "1.7.0",
185
193
  "files": {
186
- "CLAUDE.md": "1c881307d422beaeaef405b9d90ec4c71679fdebe0a919353fbd9c597e26b22b",
187
- "admin/css/index.css": "dcb2d30126c2e049b03a2e2b615082a067ab4dc911c87bf128029c900e6691b4",
194
+ "CLAUDE.md": "52ee2002bf60f1a1164c52be5040351a593f38ef8366e5258032b07b10739af0",
195
+ "admin/css/index.css": "cdd7790511deef162ad30e299b4512d289583e978ba3b87d65945cdf1da3cdda",
188
196
  "admin/lib/chart.js": "b01ce5cfac773c8df8efccab50b67d3dfa99fbead0037d817553db4a6688d4d6",
189
- "admin/lib/kit.js": "d9aa90469b3f589beaff0c3fb6ad3d983ab3fd6d94c06965be639a665bfe052f",
197
+ "admin/lib/drafts.js": "9e71b939f6c98d5c25875f35b02c367a5e89e0d2c6d084d6a4de0c71128930a2",
198
+ "admin/lib/kit.js": "94e5b5d979ded3318df2d0291bc9aeeb44af945aa0e84e68f9af10f5a53ae458",
190
199
  "admin/templates/shop.html": "c5bb03f1f7cbabfbd8202f3533783eb1848103755b1ba25fdcf1fd8e9478cb8e",
191
- "admin/views/orders.js": "1c5b571027c78223f98dec2a39c1af8e33e0d810a9b34e0b176ebf85c5c9f486",
192
- "admin/views/overview.js": "97045c069c6560b3f67e1b2230bdc9c4133ed92ec6f732886470f886f11c755a",
193
- "admin/views/products.js": "e109660355aaec8db07eb65fa1169e7d2ec20bc2ec45293cc20e34f7c1bd62bf",
194
- "admin/views/sales.js": "da71b9fbb7d39e39ecd8be35b1187e88bc4b5b28f18baaeddc6ea4cd202bb330",
195
- "admin/views/settings.js": "54d4b52409d0bdd1eb4c6e4d86c07a285914659eb6b7053183f77e2fb4b9feaa",
196
- "admin/views/shop.js": "bfc2603095cda6fd8ce3b3c10428ed55d748622d020159b07d178e6f76e3e517",
200
+ "admin/views/orders.js": "8c4b3e03e91a2caa4b9a5143b4ecafd1870da27888755b4205efb1e8d8bee59e",
201
+ "admin/views/overview.js": "02241e75976cfa759d6ba738dd13ca0bd2bcb760d66a5f184becc2e472807efc",
202
+ "admin/views/products.js": "b35893d85b92db0048459431258f2a235b1f0824b3601bebc5e5d94fdcc0d10f",
203
+ "admin/views/sales.js": "001c08963e26df72f43d2357bbcc5a0f54f74f9d22bca319c23a28a50f40c124",
204
+ "admin/views/settings.js": "042ecc2ede7ac5facbde5d37bc6a3a7cbf54c8dcd0beb6a36a796d924d221a96",
205
+ "admin/views/shop.js": "b28390b8aacde1c2d395cdf92902ec655075061bbd8dd5ca0df53db851f5051c",
197
206
  "collections/shop-orders.json": "0bca8eef2d5f9108651f73d273480265c167dc983aa115ce59613d13015ca255",
198
207
  "collections/shop-products.json": "4d4613ecec7b8a5a0744c4f0a57b17a4429b461e060206a4bbdea5ad7c8c75dc",
199
208
  "config.js": "31b336560d21a802d799ed487d5c3116de898446ba3521068b10d192f4ebbb2d",
@@ -211,7 +220,7 @@
211
220
  "lib/shop.js": "baa5dfc4e410210e8ba13678997ee5752eb2ebe72fc4d3e449465d29b60b458d",
212
221
  "lib/summary.js": "cdb13ce3d1799d429059fa03f179281711dc00427ccf2eb6b8f8aa52fef26295",
213
222
  "plugin.js": "824dba8336846d443b2427ed9a5adeba11740570566fc6e89257f9847a488632",
214
- "plugin.json": "475c64d3e5f07d0dd4f1480b65630d0e9ef362418653477de9be617aac13dc10",
223
+ "plugin.json": "03a80c969e7e7acb0a07c37da23fb2867e56b109d506235254d60c270d45da94",
215
224
  "plugin.public.js": "2f92e8340c9da653401c11dd99dab033dc6772d63fa2114489192c48e9e0be00",
216
225
  "public/account.js": "5f644016c098c7879c59948d662f5997cb61680996204ecb7897baab8945d434",
217
226
  "public/cart.js": "7338141392ec653ff616eb75688d2a2ad08cff8427d7020d381f540be0037b73",
@@ -234,25 +243,27 @@
234
243
  "public/shop.css": "c01603589727c91ff4204222e594b3f52e3ca165c1ebf9e6cef6c9f006f6e57b",
235
244
  "roles/shop-customer.json": "3f3366c536c9fbf2b2ce146bb3090b008d5fbfe22752e764eb8e5a1de9cd611c",
236
245
  "templates/shop.html": "4649e57d23458833a157799dc80141824eb0f658d96734bd1859c68e0ba9a8b9",
237
- "tests/accounts.test.js": "e64adc2a855d4ca017682bba7f75b052534f826556d8d35d825fb79cbc66596a",
246
+ "tests/accounts.test.js": "79b0e91e4310d076960b2a768ada02185b6cf05cab83052ee825b13978e0a9dc",
238
247
  "tests/api.test.js": "15c71751a3d31171707cbb3119d21b85c1c59fb7a67272b318dd4655809ac8d2",
239
248
  "tests/commission.test.js": "fbc4cf83e0ed6446a09e57e4ff37a9608c81947c633c78ac6049eb0aedf6cb58",
249
+ "tests/drafts.test.js": "2dec0cfeadd6df1d3b90f3fd8bce1f33762811eb6e94cd7325bcb8ec31edac42",
240
250
  "tests/pure.test.js": "59a351c813784e8131b96a118c79de2e3858a2fba0fc5c0a6baed79e28cafda8",
241
251
  "tests/refunds.test.js": "0ae06afca3cf9fd1fe89f1fbc63b3676216c79e174dbcb83788180c01d078366",
242
252
  "tests/sales.test.js": "9025bc608b04bcc14f5bb2c98b4f7c35de99f3ddab4ffa59772d045795bf2cb7"
243
253
  }
244
254
  },
245
255
  "support": {
246
- "version": "1.1.6",
256
+ "version": "1.2.0",
247
257
  "files": {
248
- "CLAUDE.md": "65845bbf6a909cd523466896874e84a2a29d239fdc5da0840df5155913cdcf23",
249
- "admin/css/index.css": "6695029b1da2a2e4ff25db4abecd360895ed39e4a9bc7f0996857672faf2c426",
250
- "admin/lib/kit.js": "3d5a5fb5c53f7cc9ff98664803ff58701a4d8598068e9f1a16e9db0a72109c40",
251
- "admin/lib/settings.js": "3f0b7c4c5ab927b2785af2abb3872ffd9269d12b402468f79795c4198eb49ea0",
258
+ "CLAUDE.md": "99e2856a5d57486c876126b9fb8249ff44c2ac5af1a8b471c0bbf8b888102e2a",
259
+ "admin/css/index.css": "e7fefb4e6d58010d8fd4c6acdf4e9597506a11a25f0fd4a450377551723ae89e",
260
+ "admin/lib/drafts.js": "fe60c4792d34f4f92b19908ab9d030704992e463a26c0bdd6aac0284ec643d1a",
261
+ "admin/lib/kit.js": "1208f0b2df2bb4e4d9366fda4c62464e783a10cb9e3477335c54cd14ca2da407",
262
+ "admin/lib/settings.js": "293196cded3dbc72a092869e870a75ca587560bf76ffca3c5ab9b90c1fa09b33",
252
263
  "admin/templates/support.html": "2bb69d0443a367a402e6160fe68f4af3e8983b578cfff9575b4df9a534fcb480",
253
- "admin/views/desk.js": "4f3cffe3603592d715c6a9b553e02dc7331a2675cabaeddff709c91436531734",
254
- "admin/views/inbox.js": "8134d91e20a19eb2ca7b25edb604fa241e65b764e0d058ee5d8751f903a16c32",
255
- "admin/views/support.js": "d1c72cc2e35609e54be296791cdcceb43be9be016b6c307000ddb8a226ba6d92",
264
+ "admin/views/desk.js": "5da6fa6a48cd2bbf628b7effcd72e1006338938d001cd4432eae9ccbb8fe7782",
265
+ "admin/views/inbox.js": "dea7954f982317288f9517840f6cf85728e5e011bd708c4448984766f6f8b45d",
266
+ "admin/views/support.js": "0319c9150f76edc5456cc66145ce00ef082710a6c0aa3c6febba19a98f546ec9",
256
267
  "collections/support-tickets.json": "55a0856bacd66c2e8f1e3c85bdf3638e5ff826560943cda854a8f2d336a88905",
257
268
  "config.js": "eb7ccc1169fa8b2be52a213536825492fedaec5fa73fd749cba9f498dd54b503",
258
269
  "docs/guide.md": "bfe570f4f485f58ae3aab4f71039e5e426f85f6f348c96794dfcb2dc494fced1",
@@ -263,8 +274,9 @@
263
274
  "lib/sender.js": "2b308422ac60a8a2801570e06319d5972797a601640c67beee95ccab9ac64092",
264
275
  "lib/shape.js": "d60b48410aa5582ea80451c3d831686b0e29525a958733a8bc9dfaae8eca94d2",
265
276
  "plugin.js": "91fff8d66bf5fa0fa4502bb177460ae234f0ed0494a4fbe6dd94d63e2f88b571",
266
- "plugin.json": "70bf4f117c4b2f9132365f73a1b1a38c3dfd5618d5273a8c899a2393323ebf43",
277
+ "plugin.json": "e6e7eb5058e247be225ced7d09463e6c8c66af6904b042df726b10f6bd148b2a",
267
278
  "tests/api.test.js": "1af65f0cba8a83c080a6e3e65544abc86fc43c8e25ca05c7f3e1ab4e3f7ac5a2",
279
+ "tests/drafts.test.js": "08d3920cf77b61fc92cb7785853afd8a5ff55c8851c1a7237a34464fd82e842e",
268
280
  "tests/settings.test.js": "434acbfd940eef800b1166a9aaf36363351af5652fcfb06527b8498866b7b964",
269
281
  "tests/shape.test.js": "7866c6c1d140716bf0817449937d6d711d7c3b97be1db1e1eb33bde341e36296"
270
282
  }
@@ -54,3 +54,12 @@ what strangers typed). Public data (emails, addresses, user-agents) only through
54
54
 
55
55
  ## Tests
56
56
  `tests/security.test.js` (pure). Headless: throwaway CMS with TRUST_PROXY=loopback and X-Forwarded-For.
57
+
58
+ ## 1.2.0 (2026-10-15 Tool Upgrade, second pass - ported from Security Pro 1.4.x)
59
+
60
+ - Settings drafts in `security.settings-draft.<user>` = {changes (only the fields changed), was: fingerprint(settings),
61
+ at}, laid over the current settings when kept; offered on mount as "Throw away…?" (OK throws away; Escape/backdrop
62
+ keep), removed only once in a panel; the panel is locked and cannot close while saving; rule form kept in
63
+ `security.rule-draft.<user>`. Pure parts in `admin/lib/screen-state.js` (Pro's copy adds Audit and 2FA).
64
+ - Every list: click/Enter/Shift+F10/right-click/⋮ open the row menu (a check row opens its details on click). Filter
65
+ counts follow the search; skeletons; help popovers started after mount. Keep this screen alike with Security Pro's.
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Security - the screen's pure decisions (no DOM, no storage): what the
3
+ * Sign-ins filters count, and how a kept settings draft knows it has been
4
+ * overtaken. Kept alike with Security Pro's admin/lib/screen-state.js (which
5
+ * adds the Audit and Two-factor parts). Tested in tests/screen-state.test.js.
6
+ */
7
+
8
+ /** Stable JSON: object keys sorted, so two equal settings always print alike. */
9
+ export function stableJson(value) {
10
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(',')}]`;
11
+ if (value && typeof value === 'object') {
12
+ return `{${Object.keys(value).sort().map(k => `${JSON.stringify(k)}:${stableJson(value[k])}`).join(',')}}`;
13
+ }
14
+ return JSON.stringify(value ?? null);
15
+ }
16
+
17
+ /**
18
+ * A short fingerprint of the settings as stored (GET /settings has no
19
+ * updatedAt): a kept draft carries the one it started from as `was`.
20
+ */
21
+ export function fingerprint(value) {
22
+ const s = stableJson(value);
23
+ let h = 5381;
24
+ for (let i = 0; i < s.length; i++) h = ((h * 33) ^ s.charCodeAt(i)) >>> 0;
25
+ return `${h.toString(36)}.${s.length.toString(36)}`;
26
+ }
27
+
28
+ /** The browser key a user's kept settings draft lives under ('' when nobody is signed in). */
29
+ export function draftKey(userId) {
30
+ return userId ? `security.settings-draft.${userId}` : '';
31
+ }
32
+
33
+ /**
34
+ * Is a stored value a settings draft we can offer? Whatever an older build (or
35
+ * another tab) left there is checked, never trusted. A draft holds only the
36
+ * fields the person changed (`changes`); one from before that (`values`, every
37
+ * field) is still read - `full` says so, and its values are laid over the
38
+ * settings as before, since its `was` is a fingerprint, not a copy to compare.
39
+ *
40
+ * @returns {{changes: object, was: string, at: string, full: boolean}|null}
41
+ */
42
+ export function usableDraft(raw) {
43
+ if (!raw || typeof raw !== 'object') return null;
44
+ const isMap = (v) => Boolean(v) && typeof v === 'object' && !Array.isArray(v);
45
+ const full = !isMap(raw.changes);
46
+ const changes = full ? raw.values : raw.changes;
47
+ if (!isMap(changes) || !Object.keys(changes).length) return null;
48
+ return {changes, was: typeof raw.was === 'string' ? raw.was : '', at: typeof raw.at === 'string' ? raw.at : '', full};
49
+ }
50
+
51
+ /**
52
+ * What to add to the "pick up where you left off?" question when the settings
53
+ * were saved again (here, in another tab, or by someone else) after the draft
54
+ * was begun. '' when they were not, or the draft is too old to know.
55
+ */
56
+ export function draftNote(draft, nowFingerprint) {
57
+ if (!draft?.was || !nowFingerprint || draft.was === nowFingerprint) return '';
58
+ return draft.full
59
+ ? 'The settings have been changed and saved since - picking up puts your older changes over them once you Save.'
60
+ : 'The settings have been changed and saved since: those later changes stay, apart from the fields you changed.';
61
+ }
62
+
63
+ /** Only the keys the form has, so a stale draft can never add a field. */
64
+ export function draftValues(values, known) {
65
+ const out = {};
66
+ for (const k of known) if (Object.hasOwn(values || {}, k)) out[k] = values[k];
67
+ return out;
68
+ }
69
+
70
+ /**
71
+ * The fields the person changed: the form's values against the ones it opened
72
+ * with. A draft keeps only these - laid over the whole form, a draft that only
73
+ * moved one number would undo every other setting someone saved since.
74
+ */
75
+ export function settingsChanges(values, base) {
76
+ const out = {};
77
+ for (const k of Object.keys(values || {})) if (stableJson(values[k]) !== stableJson(base?.[k])) out[k] = values[k];
78
+ return out;
79
+ }
80
+
81
+ /** A draft's changes over the form as it is NOW (the stored settings); everything it did not change stays. */
82
+ export function applySettingsChanges(base, changes) {
83
+ const out = {...(base || {})};
84
+ const c = changes && typeof changes === 'object' ? changes : {};
85
+ for (const k of Object.keys(out)) if (Object.hasOwn(c, k)) out[k] = c[k];
86
+ return out;
87
+ }
88
+
89
+ // ---- Sign-ins ---------------------------------------------------------------
90
+
91
+ const isReset = (e) => e?.kind === 'reset';
92
+
93
+ /** Does a sign-in log line match the search box and the "only this address" filter? */
94
+ export function signinMatches(e, {query = '', ip = ''} = {}) {
95
+ const q = String(query).trim().toLowerCase();
96
+ return (!ip || e.ip === ip) && (!q || [e.email, e.ip, e.ua, e.reason].some(v => String(v || '').toLowerCase().includes(q)));
97
+ }
98
+
99
+ /** Does it belong under this result button (all / failed / ok / resets)? */
100
+ export function signinInResult(e, result) {
101
+ if (result === 'all') return true;
102
+ if (result === 'resets') return isReset(e);
103
+ return !isReset(e) && (result === 'ok') === Boolean(e.ok);
104
+ }
105
+
106
+ /** The count on each result button, after the search and the address filter. */
107
+ export function signinCounts(entries, filter = {}) {
108
+ const out = {all: 0, failed: 0, ok: 0, resets: 0};
109
+ for (const e of entries || []) {
110
+ if (!signinMatches(e, filter)) continue;
111
+ out.all++;
112
+ if (isReset(e)) out.resets++;
113
+ else if (e.ok) out.ok++;
114
+ else out.failed++;
115
+ }
116
+ return out;
117
+ }
118
+
119
+ /** "Failed 3" - a button's label with its count (no count while nothing is loaded). */
120
+ export const withCount = (label, n) => (Number.isFinite(n) ? `${label} ${n.toLocaleString('en-GB')}` : label);
@@ -31,6 +31,7 @@
31
31
  <span class="scx-spacer"></span>
32
32
  <small class="scx-muted" data-bind-text="asOf.value"></small>
33
33
  </div>
34
+ <div class="scx-note scx-loading scx-checks-loading" data-bind-hidden="!checksLoading.value">Loading…</div>
34
35
  <div class="scx-checks" data-each="checks.value key=id"></div>
35
36
  </div>
36
37
  </section>
@@ -39,10 +40,10 @@
39
40
  <section class="scx-panel" data-bind-hidden="tab.value !== 'signins'">
40
41
  <div class="scx-toolbar">
41
42
  <div class="scx-seg" role="group" aria-label="Which sign-ins">
42
- <button type="button" data-on-click="res_all" data-bind-class="result.value === 'all' && 'is-on'">All</button>
43
- <button type="button" data-on-click="res_failed" data-bind-class="result.value === 'failed' && 'is-on'">Failed</button>
44
- <button type="button" data-on-click="res_ok" data-bind-class="result.value === 'ok' && 'is-on'">Succeeded</button>
45
- <button type="button" data-on-click="res_resets" data-bind-class="result.value === 'resets' && 'is-on'">Resets</button>
43
+ <button type="button" data-on-click="res_all" data-bind-class="result.value === 'all' && 'is-on'" data-bind-text="resAll.value">All</button>
44
+ <button type="button" data-on-click="res_failed" data-bind-class="result.value === 'failed' && 'is-on'" data-bind-text="resFailed.value">Failed</button>
45
+ <button type="button" data-on-click="res_ok" data-bind-class="result.value === 'ok' && 'is-on'" data-bind-text="resOk.value">Succeeded</button>
46
+ <button type="button" data-on-click="res_resets" data-bind-class="result.value === 'resets' && 'is-on'" data-bind-text="resResets.value">Resets</button>
46
47
  </div>
47
48
  <select class="form-input scx-days" aria-label="Period" data-model="days.value">
48
49
  <option value="1">Last 24 hours</option><option value="7">Last 7 days</option><option value="30">Last 30 days</option><option value="365">All kept</option>
@@ -51,17 +52,19 @@
51
52
  <span class="scx-spacer"></span>
52
53
  <small class="scx-muted" data-bind-text="signinText.value"></small>
53
54
  <button type="button" class="btn btn-sm btn-ghost" data-on-click="exportCsv"><span data-icon="download" data-icon-size="13"></span> CSV</button>
54
- <span data-help-title="Sign-ins" data-help="Every sign-in attempt, kept for the days set in the settings. Right-click (or ⋮) a row to block its address, unlock the account, or see only that address."></span>
55
+ <span data-help-title="Sign-ins" data-help="Every sign-in attempt, kept for the days set in the settings. The numbers on the buttons count what the search leaves. Click (or right-click, or ⋮) a row to block its address, unlock the account, or see only that address."></span>
55
56
  </div>
56
57
  <div class="scx-card scx-flush">
57
- <p class="scx-note scx-pad" data-bind-hidden="signinRows.value.length" data-bind-text="signinEmpty.value"></p>
58
+ <div class="scx-note scx-loading scx-pad scx-signins-loading" data-bind-hidden="!signinsLoading.value">Loading…</div>
59
+ <p class="scx-note scx-pad" data-bind-hidden="signinsLoading.value || signinRows.value.length" data-bind-text="signinEmpty.value"></p>
58
60
  <div class="scx-list scx-signins" data-each="signinRows.value key=id"></div>
59
61
  </div>
60
62
  </section>
61
63
 
62
64
  <!-- Blocked -->
63
65
  <section class="scx-panel" data-bind-hidden="tab.value !== 'blocked'">
64
- <div class="scx-card">
66
+ <div class="scx-note scx-loading scx-blocks-loading" data-bind-hidden="!blocksLoading.value">Loading…</div>
67
+ <div class="scx-card" data-bind-hidden="blocksLoading.value">
65
68
  <div class="scx-card-title">Locked after wrong passwords
66
69
  <span data-help="An account or address is locked for a while after repeated wrong passwords - longer each time. Unlock one here, or with the command line (see Settings) if you are locked out yourself."></span>
67
70
  <span class="scx-spacer"></span>
@@ -70,13 +73,13 @@
70
73
  <p class="scx-note" data-bind-hidden="lockRows.value.length">Nothing is locked.</p>
71
74
  <div class="scx-list scx-locks" data-each="lockRows.value key=id"></div>
72
75
  </div>
73
- <div class="scx-card">
76
+ <div class="scx-card" data-bind-hidden="blocksLoading.value">
74
77
  <div class="scx-card-title">Blocked for probing
75
78
  <span data-help="Addresses that kept asking for things no Domma site has, blocked for the time set in the settings. An address someone signed in from in the last week is never blocked this way."></span></div>
76
79
  <p class="scx-note" data-bind-hidden="tempRows.value.length">No address is blocked for probing.</p>
77
80
  <div class="scx-list scx-temps" data-each="tempRows.value key=id"></div>
78
81
  </div>
79
- <div class="scx-card">
82
+ <div class="scx-card" data-bind-hidden="blocksLoading.value">
80
83
  <div class="scx-card-title">Blocked by hand
81
84
  <span data-help="Addresses or ranges that may not reach the site at all - 81.2.69.142, 192.168.1.*, 10.0.0.0/8 or an IPv6 prefix like 2a00:23c7:*. They get a bare 'Forbidden'."></span></div>
82
85
  <form class="scx-add" data-on-submit="addRule" data-bind-hidden="!canManage.value">
@@ -97,6 +100,8 @@
97
100
  .scx-panel { display: flex; flex-direction: column; gap: 1rem; transition: opacity .2s; }
98
101
  .scx-panel.is-busy { opacity: .6; }
99
102
  .scx-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
103
+ /* a skeleton slot: a block (never a flex row, which would squash it to a sliver) */
104
+ .scx-loading { display: block; width: 100%; }
100
105
  .scx-note.is-bad, .scx-note.is-warn { padding: .6rem .8rem; border-radius: var(--dm-radius-md); }
101
106
  .scx-note.is-bad { background: color-mix(in srgb, var(--dm-danger) 14%, transparent); color: var(--dm-danger); }
102
107
  .scx-note.is-warn { background: color-mix(in srgb, var(--dm-warning) 14%, transparent); color: var(--dm-text); }
@@ -144,7 +149,7 @@
144
149
  .scx-days, .scx-search { width: auto; padding: .25rem .5rem; font-size: var(--dm-text-sm); }
145
150
  .scx-search { flex: 0 1 15rem; }
146
151
  .scx-list { display: flex; flex-direction: column; max-height: 34rem; overflow-y: auto; }
147
- .scx-row { display: grid; align-items: center; gap: .6rem; padding: .45rem .8rem; border-bottom: 1px solid var(--dm-border); font-size: var(--dm-text-sm); cursor: default; }
152
+ .scx-row { display: grid; align-items: center; gap: .6rem; padding: .45rem .8rem; border-bottom: 1px solid var(--dm-border); font-size: var(--dm-text-sm); cursor: pointer; }
148
153
  .scx-row:last-child { border-bottom: 0; }
149
154
  .scx-row:hover, .scx-row:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
150
155
  .scx-signins .scx-row { grid-template-columns: 8.5rem 5.5rem minmax(8rem, 1.3fr) 8rem minmax(6rem, 1fr) 1.5rem; }