@datum-cloud/activity-ui 0.6.0 → 0.7.0-dev.f256686

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/dist/index.js CHANGED
@@ -1198,6 +1198,48 @@ function constructNow(date) {
1198
1198
  return constructFrom(date, Date.now());
1199
1199
  }
1200
1200
 
1201
+ /**
1202
+ * The {@link isSameDay} function options.
1203
+ */
1204
+
1205
+ /**
1206
+ * @name isSameDay
1207
+ * @category Day Helpers
1208
+ * @summary Are the given dates in the same day (and year and month)?
1209
+ *
1210
+ * @description
1211
+ * Are the given dates in the same day (and year and month)?
1212
+ *
1213
+ * @param laterDate - The first date to check
1214
+ * @param earlierDate - The second date to check
1215
+ * @param options - An object with options
1216
+ *
1217
+ * @returns The dates are in the same day (and year and month)
1218
+ *
1219
+ * @example
1220
+ * // Are 4 September 06:00:00 and 4 September 18:00:00 in the same day?
1221
+ * const result = isSameDay(new Date(2014, 8, 4, 6, 0), new Date(2014, 8, 4, 18, 0))
1222
+ * //=> true
1223
+ *
1224
+ * @example
1225
+ * // Are 4 September and 4 October in the same day?
1226
+ * const result = isSameDay(new Date(2014, 8, 4), new Date(2014, 9, 4))
1227
+ * //=> false
1228
+ *
1229
+ * @example
1230
+ * // Are 4 September, 2014 and 4 September, 2015 in the same day?
1231
+ * const result = isSameDay(new Date(2014, 8, 4), new Date(2015, 8, 4))
1232
+ * //=> false
1233
+ */
1234
+ function isSameDay(laterDate, earlierDate, options) {
1235
+ const [dateLeft_, dateRight_] = normalizeDates(
1236
+ options?.in,
1237
+ laterDate,
1238
+ earlierDate,
1239
+ );
1240
+ return +startOfDay(dateLeft_) === +startOfDay(dateRight_);
1241
+ }
1242
+
1201
1243
  /**
1202
1244
  * @name isDate
1203
1245
  * @category Common Helpers
@@ -3934,6 +3976,36 @@ function formatISO(date, options) {
3934
3976
  return result;
3935
3977
  }
3936
3978
 
3979
+ /**
3980
+ * The {@link isToday} function options.
3981
+ */
3982
+
3983
+ /**
3984
+ * @name isToday
3985
+ * @category Day Helpers
3986
+ * @summary Is the given date today?
3987
+ * @pure false
3988
+ *
3989
+ * @description
3990
+ * Is the given date today?
3991
+ *
3992
+ * @param date - The date to check
3993
+ * @param options - An object with options
3994
+ *
3995
+ * @returns The date is today
3996
+ *
3997
+ * @example
3998
+ * // If today is 6 October 2014, is 6 October 14:00:00 today?
3999
+ * const result = isToday(new Date(2014, 9, 6, 14, 0))
4000
+ * //=> true
4001
+ */
4002
+ function isToday(date, options) {
4003
+ return isSameDay(
4004
+ constructFrom(date, date),
4005
+ constructNow(date),
4006
+ );
4007
+ }
4008
+
3937
4009
  /**
3938
4010
  * The {@link subDays} function options.
3939
4011
  */
@@ -3961,6 +4033,36 @@ function subDays(date, amount, options) {
3961
4033
  return addDays(date, -amount, options);
3962
4034
  }
3963
4035
 
4036
+ /**
4037
+ * The {@link isYesterday} function options.
4038
+ */
4039
+
4040
+ /**
4041
+ * @name isYesterday
4042
+ * @category Day Helpers
4043
+ * @summary Is the given date yesterday?
4044
+ * @pure false
4045
+ *
4046
+ * @description
4047
+ * Is the given date yesterday?
4048
+ *
4049
+ * @param date - The date to check
4050
+ * @param options - An object with options
4051
+ *
4052
+ * @returns The date is yesterday
4053
+ *
4054
+ * @example
4055
+ * // If today is 6 October 2014, is 5 October 14:00:00 yesterday?
4056
+ * const result = isYesterday(new Date(2014, 9, 5, 14, 0))
4057
+ * //=> true
4058
+ */
4059
+ function isYesterday(date, options) {
4060
+ return isSameDay(
4061
+ constructFrom(date, date),
4062
+ subDays(constructNow(date), 1),
4063
+ );
4064
+ }
4065
+
3964
4066
  /**
3965
4067
  * The {@link subHours} function options.
3966
4068
  */
@@ -7902,7 +8004,7 @@ const createLucideIcon = (iconName, iconNode) => {
7902
8004
  */
7903
8005
 
7904
8006
 
7905
- const __iconNode$n = [
8007
+ const __iconNode$o = [
7906
8008
  [
7907
8009
  "path",
7908
8010
  {
@@ -7911,7 +8013,7 @@ const __iconNode$n = [
7911
8013
  }
7912
8014
  ]
7913
8015
  ];
7914
- const Activity = createLucideIcon("activity", __iconNode$n);
8016
+ const Activity = createLucideIcon("activity", __iconNode$o);
7915
8017
 
7916
8018
  /**
7917
8019
  * @license lucide-react v0.577.0 - ISC
@@ -7921,7 +8023,7 @@ const Activity = createLucideIcon("activity", __iconNode$n);
7921
8023
  */
7922
8024
 
7923
8025
 
7924
- const __iconNode$m = [
8026
+ const __iconNode$n = [
7925
8027
  ["path", { d: "M10.268 21a2 2 0 0 0 3.464 0", key: "vwvbt9" }],
7926
8028
  [
7927
8029
  "path",
@@ -7931,7 +8033,7 @@ const __iconNode$m = [
7931
8033
  }
7932
8034
  ]
7933
8035
  ];
7934
- const Bell = createLucideIcon("bell", __iconNode$m);
8036
+ const Bell = createLucideIcon("bell", __iconNode$n);
7935
8037
 
7936
8038
  /**
7937
8039
  * @license lucide-react v0.577.0 - ISC
@@ -7941,8 +8043,8 @@ const Bell = createLucideIcon("bell", __iconNode$m);
7941
8043
  */
7942
8044
 
7943
8045
 
7944
- const __iconNode$l = [["path", { d: "M20 6 9 17l-5-5", key: "1gmf2c" }]];
7945
- const Check = createLucideIcon("check", __iconNode$l);
8046
+ const __iconNode$m = [["path", { d: "M20 6 9 17l-5-5", key: "1gmf2c" }]];
8047
+ const Check = createLucideIcon("check", __iconNode$m);
7946
8048
 
7947
8049
  /**
7948
8050
  * @license lucide-react v0.577.0 - ISC
@@ -7952,8 +8054,8 @@ const Check = createLucideIcon("check", __iconNode$l);
7952
8054
  */
7953
8055
 
7954
8056
 
7955
- const __iconNode$k = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
7956
- const ChevronDown = createLucideIcon("chevron-down", __iconNode$k);
8057
+ const __iconNode$l = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
8058
+ const ChevronDown = createLucideIcon("chevron-down", __iconNode$l);
7957
8059
 
7958
8060
  /**
7959
8061
  * @license lucide-react v0.577.0 - ISC
@@ -7963,8 +8065,8 @@ const ChevronDown = createLucideIcon("chevron-down", __iconNode$k);
7963
8065
  */
7964
8066
 
7965
8067
 
7966
- const __iconNode$j = [["path", { d: "m9 18 6-6-6-6", key: "mthhwq" }]];
7967
- const ChevronRight = createLucideIcon("chevron-right", __iconNode$j);
8068
+ const __iconNode$k = [["path", { d: "m9 18 6-6-6-6", key: "mthhwq" }]];
8069
+ const ChevronRight = createLucideIcon("chevron-right", __iconNode$k);
7968
8070
 
7969
8071
  /**
7970
8072
  * @license lucide-react v0.577.0 - ISC
@@ -7974,8 +8076,8 @@ const ChevronRight = createLucideIcon("chevron-right", __iconNode$j);
7974
8076
  */
7975
8077
 
7976
8078
 
7977
- const __iconNode$i = [["path", { d: "m18 15-6-6-6 6", key: "153udz" }]];
7978
- const ChevronUp = createLucideIcon("chevron-up", __iconNode$i);
8079
+ const __iconNode$j = [["path", { d: "m18 15-6-6-6 6", key: "153udz" }]];
8080
+ const ChevronUp = createLucideIcon("chevron-up", __iconNode$j);
7979
8081
 
7980
8082
  /**
7981
8083
  * @license lucide-react v0.577.0 - ISC
@@ -7985,12 +8087,12 @@ const ChevronUp = createLucideIcon("chevron-up", __iconNode$i);
7985
8087
  */
7986
8088
 
7987
8089
 
7988
- const __iconNode$h = [
8090
+ const __iconNode$i = [
7989
8091
  ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
7990
8092
  ["line", { x1: "12", x2: "12", y1: "8", y2: "12", key: "1pkeuh" }],
7991
8093
  ["line", { x1: "12", x2: "12.01", y1: "16", y2: "16", key: "4dfq90" }]
7992
8094
  ];
7993
- const CircleAlert = createLucideIcon("circle-alert", __iconNode$h);
8095
+ const CircleAlert = createLucideIcon("circle-alert", __iconNode$i);
7994
8096
 
7995
8097
  /**
7996
8098
  * @license lucide-react v0.577.0 - ISC
@@ -8000,11 +8102,11 @@ const CircleAlert = createLucideIcon("circle-alert", __iconNode$h);
8000
8102
  */
8001
8103
 
8002
8104
 
8003
- const __iconNode$g = [
8105
+ const __iconNode$h = [
8004
8106
  ["path", { d: "M21.801 10A10 10 0 1 1 17 3.335", key: "yps3ct" }],
8005
8107
  ["path", { d: "m9 11 3 3L22 4", key: "1pflzl" }]
8006
8108
  ];
8007
- const CircleCheckBig = createLucideIcon("circle-check-big", __iconNode$g);
8109
+ const CircleCheckBig = createLucideIcon("circle-check-big", __iconNode$h);
8008
8110
 
8009
8111
  /**
8010
8112
  * @license lucide-react v0.577.0 - ISC
@@ -8014,11 +8116,11 @@ const CircleCheckBig = createLucideIcon("circle-check-big", __iconNode$g);
8014
8116
  */
8015
8117
 
8016
8118
 
8017
- const __iconNode$f = [
8119
+ const __iconNode$g = [
8018
8120
  ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
8019
8121
  ["path", { d: "m9 12 2 2 4-4", key: "dzmm74" }]
8020
8122
  ];
8021
- const CircleCheck = createLucideIcon("circle-check", __iconNode$f);
8123
+ const CircleCheck = createLucideIcon("circle-check", __iconNode$g);
8022
8124
 
8023
8125
  /**
8024
8126
  * @license lucide-react v0.577.0 - ISC
@@ -8028,12 +8130,12 @@ const CircleCheck = createLucideIcon("circle-check", __iconNode$f);
8028
8130
  */
8029
8131
 
8030
8132
 
8031
- const __iconNode$e = [
8133
+ const __iconNode$f = [
8032
8134
  ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
8033
8135
  ["path", { d: "m15 9-6 6", key: "1uzhvr" }],
8034
8136
  ["path", { d: "m9 9 6 6", key: "z0biqf" }]
8035
8137
  ];
8036
- const CircleX = createLucideIcon("circle-x", __iconNode$e);
8138
+ const CircleX = createLucideIcon("circle-x", __iconNode$f);
8037
8139
 
8038
8140
  /**
8039
8141
  * @license lucide-react v0.577.0 - ISC
@@ -8043,11 +8145,11 @@ const CircleX = createLucideIcon("circle-x", __iconNode$e);
8043
8145
  */
8044
8146
 
8045
8147
 
8046
- const __iconNode$d = [
8148
+ const __iconNode$e = [
8047
8149
  ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
8048
8150
  ["path", { d: "M12 6v6l4 2", key: "mmk7yg" }]
8049
8151
  ];
8050
- const Clock = createLucideIcon("clock", __iconNode$d);
8152
+ const Clock = createLucideIcon("clock", __iconNode$e);
8051
8153
 
8052
8154
  /**
8053
8155
  * @license lucide-react v0.577.0 - ISC
@@ -8057,11 +8159,11 @@ const Clock = createLucideIcon("clock", __iconNode$d);
8057
8159
  */
8058
8160
 
8059
8161
 
8060
- const __iconNode$c = [
8162
+ const __iconNode$d = [
8061
8163
  ["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2", key: "17jyea" }],
8062
8164
  ["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2", key: "zix9uf" }]
8063
8165
  ];
8064
- const Copy = createLucideIcon("copy", __iconNode$c);
8166
+ const Copy = createLucideIcon("copy", __iconNode$d);
8065
8167
 
8066
8168
  /**
8067
8169
  * @license lucide-react v0.577.0 - ISC
@@ -8071,7 +8173,7 @@ const Copy = createLucideIcon("copy", __iconNode$c);
8071
8173
  */
8072
8174
 
8073
8175
 
8074
- const __iconNode$b = [
8176
+ const __iconNode$c = [
8075
8177
  [
8076
8178
  "path",
8077
8179
  {
@@ -8081,7 +8183,7 @@ const __iconNode$b = [
8081
8183
  ],
8082
8184
  ["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }]
8083
8185
  ];
8084
- const Eye = createLucideIcon("eye", __iconNode$b);
8186
+ const Eye = createLucideIcon("eye", __iconNode$c);
8085
8187
 
8086
8188
  /**
8087
8189
  * @license lucide-react v0.577.0 - ISC
@@ -8091,8 +8193,8 @@ const Eye = createLucideIcon("eye", __iconNode$b);
8091
8193
  */
8092
8194
 
8093
8195
 
8094
- const __iconNode$a = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
8095
- const LoaderCircle = createLucideIcon("loader-circle", __iconNode$a);
8196
+ const __iconNode$b = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]];
8197
+ const LoaderCircle = createLucideIcon("loader-circle", __iconNode$b);
8096
8198
 
8097
8199
  /**
8098
8200
  * @license lucide-react v0.577.0 - ISC
@@ -8102,7 +8204,7 @@ const LoaderCircle = createLucideIcon("loader-circle", __iconNode$a);
8102
8204
  */
8103
8205
 
8104
8206
 
8105
- const __iconNode$9 = [
8207
+ const __iconNode$a = [
8106
8208
  [
8107
8209
  "path",
8108
8210
  {
@@ -8112,7 +8214,7 @@ const __iconNode$9 = [
8112
8214
  ],
8113
8215
  ["path", { d: "m15 5 4 4", key: "1mk7zo" }]
8114
8216
  ];
8115
- const Pencil = createLucideIcon("pencil", __iconNode$9);
8217
+ const Pencil = createLucideIcon("pencil", __iconNode$a);
8116
8218
 
8117
8219
  /**
8118
8220
  * @license lucide-react v0.577.0 - ISC
@@ -8122,11 +8224,11 @@ const Pencil = createLucideIcon("pencil", __iconNode$9);
8122
8224
  */
8123
8225
 
8124
8226
 
8125
- const __iconNode$8 = [
8227
+ const __iconNode$9 = [
8126
8228
  ["path", { d: "M5 12h14", key: "1ays0h" }],
8127
8229
  ["path", { d: "M12 5v14", key: "s699le" }]
8128
8230
  ];
8129
- const Plus = createLucideIcon("plus", __iconNode$8);
8231
+ const Plus = createLucideIcon("plus", __iconNode$9);
8130
8232
 
8131
8233
  /**
8132
8234
  * @license lucide-react v0.577.0 - ISC
@@ -8136,13 +8238,13 @@ const Plus = createLucideIcon("plus", __iconNode$8);
8136
8238
  */
8137
8239
 
8138
8240
 
8139
- const __iconNode$7 = [
8241
+ const __iconNode$8 = [
8140
8242
  ["path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8", key: "v9h5vc" }],
8141
8243
  ["path", { d: "M21 3v5h-5", key: "1q7to0" }],
8142
8244
  ["path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16", key: "3uifl3" }],
8143
8245
  ["path", { d: "M8 16H3v5", key: "1cv678" }]
8144
8246
  ];
8145
- const RefreshCw = createLucideIcon("refresh-cw", __iconNode$7);
8247
+ const RefreshCw = createLucideIcon("refresh-cw", __iconNode$8);
8146
8248
 
8147
8249
  /**
8148
8250
  * @license lucide-react v0.577.0 - ISC
@@ -8152,11 +8254,11 @@ const RefreshCw = createLucideIcon("refresh-cw", __iconNode$7);
8152
8254
  */
8153
8255
 
8154
8256
 
8155
- const __iconNode$6 = [
8257
+ const __iconNode$7 = [
8156
8258
  ["path", { d: "M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8", key: "1p45f6" }],
8157
8259
  ["path", { d: "M21 3v5h-5", key: "1q7to0" }]
8158
8260
  ];
8159
- const RotateCw = createLucideIcon("rotate-cw", __iconNode$6);
8261
+ const RotateCw = createLucideIcon("rotate-cw", __iconNode$7);
8160
8262
 
8161
8263
  /**
8162
8264
  * @license lucide-react v0.577.0 - ISC
@@ -8166,11 +8268,32 @@ const RotateCw = createLucideIcon("rotate-cw", __iconNode$6);
8166
8268
  */
8167
8269
 
8168
8270
 
8169
- const __iconNode$5 = [
8271
+ const __iconNode$6 = [
8170
8272
  ["path", { d: "m21 21-4.34-4.34", key: "14j7rj" }],
8171
8273
  ["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }]
8172
8274
  ];
8173
- const Search = createLucideIcon("search", __iconNode$5);
8275
+ const Search = createLucideIcon("search", __iconNode$6);
8276
+
8277
+ /**
8278
+ * @license lucide-react v0.577.0 - ISC
8279
+ *
8280
+ * This source code is licensed under the ISC license.
8281
+ * See the LICENSE file in the root directory of this source tree.
8282
+ */
8283
+
8284
+
8285
+ const __iconNode$5 = [
8286
+ ["path", { d: "M10 5H3", key: "1qgfaw" }],
8287
+ ["path", { d: "M12 19H3", key: "yhmn1j" }],
8288
+ ["path", { d: "M14 3v4", key: "1sua03" }],
8289
+ ["path", { d: "M16 17v4", key: "1q0r14" }],
8290
+ ["path", { d: "M21 12h-9", key: "1o4lsq" }],
8291
+ ["path", { d: "M21 19h-5", key: "1rlt1p" }],
8292
+ ["path", { d: "M21 5h-7", key: "1oszz2" }],
8293
+ ["path", { d: "M8 10v4", key: "tgpxqk" }],
8294
+ ["path", { d: "M8 12H3", key: "a7s4jb" }]
8295
+ ];
8296
+ const SlidersHorizontal = createLucideIcon("sliders-horizontal", __iconNode$5);
8174
8297
 
8175
8298
  /**
8176
8299
  * @license lucide-react v0.577.0 - ISC
@@ -9671,6 +9794,14 @@ function ActionToggle({ value, onChange, className = '', disabled = false, }) {
9671
9794
 
9672
9795
  // Debounce delay for filter changes (ms)
9673
9796
  const FILTER_DEBOUNCE_MS$1 = 300;
9797
+ // Backoff for watch stream reconnects. Without this, a persistently failing
9798
+ // upstream gets re-opened on every render with no delay — see
9799
+ // https://github.com/datum-cloud/infra/issues/5060, where a single browser
9800
+ // tab reconnected roughly 2.5 times a minute against a stream that kept
9801
+ // aborting, driving the proxy's memory up until the process was OOMKilled.
9802
+ const RECONNECT_BASE_DELAY_MS = 1000;
9803
+ const RECONNECT_MAX_DELAY_MS = 30000;
9804
+ const MAX_RECONNECT_ATTEMPTS = 8;
9674
9805
  /**
9675
9806
  * Escape a value for safe interpolation into a CEL string literal.
9676
9807
  * Prevents injection via values containing backslashes or double quotes.
@@ -9765,6 +9896,12 @@ function useActivityFeed({ client, initialFilters = {}, initialTimeRange = { sta
9765
9896
  const resourceVersionRef = React.useRef();
9766
9897
  // Track the watch stop function
9767
9898
  const watchStopRef = React.useRef(null);
9899
+ // Consecutive watch failures since the last successful event, used to back
9900
+ // off reconnect attempts. Reset on any real event (including BOOKMARK) and
9901
+ // on a manual startStreaming() call.
9902
+ const reconnectAttemptsRef = React.useRef(0);
9903
+ // Pending scheduled reconnect, if any.
9904
+ const reconnectTimerRef = React.useRef(null);
9768
9905
  // Track whether streaming should restart after filter change
9769
9906
  const shouldRestartStreamingRef = React.useRef(false);
9770
9907
  // Track if we've done the initial load
@@ -9839,6 +9976,9 @@ function useActivityFeed({ client, initialFilters = {}, initialTimeRange = { sta
9839
9976
  console.error('Watch error:', event.object);
9840
9977
  return;
9841
9978
  }
9979
+ // A real event (including a BOOKMARK keep-alive) means the connection is
9980
+ // healthy — reset backoff so a later failure starts from the base delay.
9981
+ reconnectAttemptsRef.current = 0;
9842
9982
  if (event.type === 'BOOKMARK') {
9843
9983
  // Update resource version for resume capability
9844
9984
  if (event.object.metadata?.resourceVersion) {
@@ -9904,15 +10044,13 @@ function useActivityFeed({ client, initialFilters = {}, initialTimeRange = { sta
9904
10044
  setActivities((prev) => prev.filter((a) => a.metadata?.name !== event.object.metadata?.name));
9905
10045
  }
9906
10046
  }, [filters]);
9907
- // Start watching for real-time updates
9908
- const startStreaming = React.useCallback(() => {
10047
+ // Open the watch connection. Does not touch userPaused/backoff state —
10048
+ // callers (startStreaming, scheduleReconnect) own that.
10049
+ const connectWatch = React.useCallback(() => {
9909
10050
  if (watchStopRef.current) {
9910
10051
  // Already watching
9911
10052
  return;
9912
10053
  }
9913
- // Explicit start clears the user-paused flag so the auto-restart
9914
- // effects can keep the stream alive after subsequent filter changes.
9915
- setUserPaused(false);
9916
10054
  // Clear any previous watch error when starting a new stream
9917
10055
  setWatchError(null);
9918
10056
  const params = buildWatchParams();
@@ -9926,6 +10064,7 @@ function useActivityFeed({ client, initialFilters = {}, initialTimeRange = { sta
9926
10064
  setWatchError(streamError);
9927
10065
  setIsStreaming(false);
9928
10066
  watchStopRef.current = null;
10067
+ reconnectAttemptsRef.current += 1;
9929
10068
  },
9930
10069
  onClose: () => {
9931
10070
  setIsStreaming(false);
@@ -9936,11 +10075,50 @@ function useActivityFeed({ client, initialFilters = {}, initialTimeRange = { sta
9936
10075
  setIsStreaming(true);
9937
10076
  setNewActivitiesCount(0);
9938
10077
  }, [client, buildWatchParams, handleWatchEvent]);
10078
+ // Reconnect with exponential backoff. The first attempt (no prior
10079
+ // failures) connects immediately; each subsequent failure doubles the
10080
+ // delay up to RECONNECT_MAX_DELAY_MS. After MAX_RECONNECT_ATTEMPTS
10081
+ // consecutive failures, this stops retrying automatically — the watch
10082
+ // stays stopped (with watchError set) until the user calls
10083
+ // startStreaming() again, which resets the attempt count.
10084
+ const scheduleReconnect = React.useCallback(() => {
10085
+ if (reconnectTimerRef.current) {
10086
+ // A reconnect is already scheduled.
10087
+ return;
10088
+ }
10089
+ if (reconnectAttemptsRef.current >= MAX_RECONNECT_ATTEMPTS) {
10090
+ return;
10091
+ }
10092
+ const attempt = reconnectAttemptsRef.current;
10093
+ const delay = attempt === 0 ? 0 : Math.min(RECONNECT_MAX_DELAY_MS, RECONNECT_BASE_DELAY_MS * 2 ** (attempt - 1));
10094
+ reconnectTimerRef.current = setTimeout(() => {
10095
+ reconnectTimerRef.current = null;
10096
+ connectWatch();
10097
+ }, delay);
10098
+ }, [connectWatch]);
10099
+ // Start watching for real-time updates. This is the user/effect-facing
10100
+ // entry point: it resets backoff and any pending scheduled reconnect
10101
+ // before connecting, so a manual retry always starts from a clean slate.
10102
+ const startStreaming = React.useCallback(() => {
10103
+ // Explicit start clears the user-paused flag so the auto-restart
10104
+ // effects can keep the stream alive after subsequent filter changes.
10105
+ setUserPaused(false);
10106
+ reconnectAttemptsRef.current = 0;
10107
+ if (reconnectTimerRef.current) {
10108
+ clearTimeout(reconnectTimerRef.current);
10109
+ reconnectTimerRef.current = null;
10110
+ }
10111
+ connectWatch();
10112
+ }, [connectWatch]);
9939
10113
  // Stop watching. Marks the user as paused so the auto-start effects
9940
10114
  // below don't immediately re-open the stream this call just closed.
9941
10115
  const stopStreaming = React.useCallback(() => {
9942
10116
  setUserPaused(true);
9943
10117
  setIsStreaming(false);
10118
+ if (reconnectTimerRef.current) {
10119
+ clearTimeout(reconnectTimerRef.current);
10120
+ reconnectTimerRef.current = null;
10121
+ }
9944
10122
  if (watchStopRef.current) {
9945
10123
  watchStopRef.current();
9946
10124
  watchStopRef.current = null;
@@ -10103,9 +10281,10 @@ function useActivityFeed({ client, initialFilters = {}, initialTimeRange = { sta
10103
10281
  }
10104
10282
  };
10105
10283
  }, [filters, timeRange]); // eslint-disable-line react-hooks/exhaustive-deps
10106
- // Auto-start streaming after initial load when enabled. Skipped when
10107
- // the user has explicitly paused — otherwise clicking Pause would be
10108
- // immediately undone by this effect on the next render.
10284
+ // Auto-start streaming after initial load when enabled, and reconnect
10285
+ // (with backoff — see scheduleReconnect) whenever a connection drops.
10286
+ // Skipped when the user has explicitly paused — otherwise clicking Pause
10287
+ // would be immediately undone by this effect on the next render.
10109
10288
  React.useEffect(() => {
10110
10289
  if (enableStreaming &&
10111
10290
  autoStartStreaming &&
@@ -10113,9 +10292,9 @@ function useActivityFeed({ client, initialFilters = {}, initialTimeRange = { sta
10113
10292
  activities.length > 0 &&
10114
10293
  !isStreaming &&
10115
10294
  !isLoading) {
10116
- startStreaming();
10295
+ scheduleReconnect();
10117
10296
  }
10118
- }, [enableStreaming, autoStartStreaming, userPaused, activities.length, isStreaming, isLoading, startStreaming]);
10297
+ }, [enableStreaming, autoStartStreaming, userPaused, activities.length, isStreaming, isLoading, scheduleReconnect]);
10119
10298
  // Restart streaming after filter change refresh completes. Also skipped
10120
10299
  // when the user has paused — a filter change shouldn't silently resume
10121
10300
  // a stream the user intentionally stopped.
@@ -10127,15 +10306,18 @@ function useActivityFeed({ client, initialFilters = {}, initialTimeRange = { sta
10127
10306
  !isStreaming &&
10128
10307
  !isLoading) {
10129
10308
  shouldRestartStreamingRef.current = false;
10130
- startStreaming();
10309
+ scheduleReconnect();
10131
10310
  }
10132
- }, [enableStreaming, userPaused, activities.length, isStreaming, isLoading, startStreaming]);
10311
+ }, [enableStreaming, userPaused, activities.length, isStreaming, isLoading, scheduleReconnect]);
10133
10312
  // Cleanup on unmount
10134
10313
  React.useEffect(() => {
10135
10314
  return () => {
10136
10315
  if (watchStopRef.current) {
10137
10316
  watchStopRef.current();
10138
10317
  }
10318
+ if (reconnectTimerRef.current) {
10319
+ clearTimeout(reconnectTimerRef.current);
10320
+ }
10139
10321
  if (filterDebounceRef.current) {
10140
10322
  clearTimeout(filterDebounceRef.current);
10141
10323
  }
@@ -10300,36 +10482,6 @@ function ActivityExpandedDetails({ activity, compact = false, }) {
10300
10482
  return (jsxRuntime.jsx(TooltipProvider, { children: compact ? (jsxRuntime.jsx(DetailPanelShell, { children: body })) : (jsxRuntime.jsx("div", { className: "mt-4 pt-4 border-t border-border", children: body })) }));
10301
10483
  }
10302
10484
 
10303
- // Number of columns rendered for the feed variant. Used as the colSpan on
10304
- // the expanded-detail row so it stretches across the full width.
10305
- const ACTIVITY_FEED_COLUMN_COUNT = 5;
10306
- /**
10307
- * Get avatar initials from actor name
10308
- */
10309
- function getActorInitials(name) {
10310
- const parts = name.split(/[@\s.]+/).filter(Boolean);
10311
- if (parts.length === 0)
10312
- return "?";
10313
- if (parts.length === 1)
10314
- return parts[0].charAt(0).toUpperCase();
10315
- return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase();
10316
- }
10317
- /**
10318
- * Get Tailwind classes for actor avatar based on actor type
10319
- */
10320
- function getActorAvatarClasses(actorType, compact) {
10321
- const baseClasses = cn("rounded-full flex items-center justify-center shrink-0 font-semibold", compact ? "w-5 h-5 text-1xs" : "w-6 h-6 text-1xs");
10322
- switch (actorType) {
10323
- case "user":
10324
- return cn(baseClasses, "bg-primary text-primary-foreground");
10325
- case "controller":
10326
- return cn(baseClasses, "bg-secondary text-secondary-foreground");
10327
- case "machine account":
10328
- return cn(baseClasses, "bg-muted text-muted-foreground");
10329
- default:
10330
- return cn(baseClasses, "bg-muted text-muted-foreground");
10331
- }
10332
- }
10333
10485
  /**
10334
10486
  * Extract verb from activity summary (e.g., "alice created HTTPProxy" -> "created")
10335
10487
  */
@@ -10338,24 +10490,24 @@ function extractVerb(summary) {
10338
10490
  if (words.length >= 2) {
10339
10491
  return words[1].toLowerCase();
10340
10492
  }
10341
- return "unknown";
10493
+ return 'unknown';
10342
10494
  }
10343
10495
  /**
10344
- * Normalize verb to a canonical form for coloring
10496
+ * Normalize a verb to a canonical form used for coloring/icons/grouping.
10345
10497
  */
10346
10498
  function normalizeVerb(verb) {
10347
10499
  const normalized = verb.toLowerCase();
10348
- if (normalized.includes("create") || normalized.includes("add"))
10349
- return "create";
10350
- if (normalized.includes("delete") || normalized.includes("remove"))
10351
- return "delete";
10352
- if (normalized.includes("update") ||
10353
- normalized.includes("patch") ||
10354
- normalized.includes("modify") ||
10355
- normalized.includes("change") ||
10356
- normalized.includes("edit"))
10357
- return "update";
10358
- return "other";
10500
+ if (normalized.includes('create') || normalized.includes('add'))
10501
+ return 'create';
10502
+ if (normalized.includes('delete') || normalized.includes('remove'))
10503
+ return 'delete';
10504
+ if (normalized.includes('update') ||
10505
+ normalized.includes('patch') ||
10506
+ normalized.includes('modify') ||
10507
+ normalized.includes('change') ||
10508
+ normalized.includes('edit'))
10509
+ return 'update';
10510
+ return 'other';
10359
10511
  }
10360
10512
  /**
10361
10513
  * Get icon container + icon color classes based on verb
@@ -10363,25 +10515,25 @@ function normalizeVerb(verb) {
10363
10515
  function getActionIconClasses(verb) {
10364
10516
  const normalizedVerb = normalizeVerb(verb);
10365
10517
  switch (normalizedVerb) {
10366
- case "create":
10518
+ case 'create':
10367
10519
  return {
10368
- container: "bg-[var(--success-100)]",
10369
- icon: "text-[var(--success-500)]",
10520
+ container: 'bg-blue-50 dark:bg-blue-950',
10521
+ icon: 'text-blue-500 dark:text-blue-400',
10370
10522
  };
10371
- case "update":
10523
+ case 'update':
10372
10524
  return {
10373
- container: "bg-[var(--info-100)]",
10374
- icon: "text-[var(--info-500)]",
10525
+ container: 'bg-green-50 dark:bg-green-950',
10526
+ icon: 'text-green-600 dark:text-green-400',
10375
10527
  };
10376
- case "delete":
10528
+ case 'delete':
10377
10529
  return {
10378
- container: "bg-destructive/10",
10379
- icon: "text-destructive",
10530
+ container: 'bg-red-50 dark:bg-red-950',
10531
+ icon: 'text-red-500 dark:text-red-400',
10380
10532
  };
10381
10533
  default:
10382
10534
  return {
10383
- container: "bg-muted",
10384
- icon: "text-muted-foreground",
10535
+ container: 'bg-slate-100 dark:bg-slate-800',
10536
+ icon: 'text-slate-500 dark:text-slate-400',
10385
10537
  };
10386
10538
  }
10387
10539
  }
@@ -10391,20 +10543,51 @@ function getActionIconClasses(verb) {
10391
10543
  function getTimelineIcon(verb) {
10392
10544
  const normalizedVerb = normalizeVerb(verb);
10393
10545
  switch (normalizedVerb) {
10394
- case "create":
10546
+ case 'create':
10395
10547
  return Plus;
10396
- case "update":
10548
+ case 'update':
10397
10549
  return Pencil;
10398
- case "delete":
10550
+ case 'delete':
10399
10551
  return Trash2;
10400
10552
  default:
10401
10553
  return Activity;
10402
10554
  }
10403
10555
  }
10556
+
10557
+ // Number of columns rendered for the feed variant. Used as the colSpan on
10558
+ // the expanded-detail row so it stretches across the full width.
10559
+ const ACTIVITY_FEED_COLUMN_COUNT = 5;
10560
+ /**
10561
+ * Get avatar initials from actor name
10562
+ */
10563
+ function getActorInitials(name) {
10564
+ const parts = name.split(/[@\s.]+/).filter(Boolean);
10565
+ if (parts.length === 0)
10566
+ return "?";
10567
+ if (parts.length === 1)
10568
+ return parts[0].charAt(0).toUpperCase();
10569
+ return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase();
10570
+ }
10571
+ /**
10572
+ * Get Tailwind classes for actor avatar based on actor type
10573
+ */
10574
+ function getActorAvatarClasses(actorType, compact) {
10575
+ const baseClasses = cn("rounded-full flex items-center justify-center shrink-0 font-semibold", compact ? "w-5 h-5 text-2xs" : "w-6 h-6 text-2xs");
10576
+ switch (actorType) {
10577
+ case "user":
10578
+ return cn(baseClasses, "bg-primary text-primary-foreground");
10579
+ case "controller":
10580
+ return cn(baseClasses, "bg-secondary text-secondary-foreground");
10581
+ case "machine account":
10582
+ return cn(baseClasses, "bg-muted text-muted-foreground");
10583
+ default:
10584
+ return cn(baseClasses, "bg-muted text-muted-foreground");
10585
+ }
10586
+ }
10404
10587
  /**
10405
10588
  * ActivityFeedItem renders a single activity in the feed or timeline
10406
10589
  */
10407
- function ActivityFeedItem({ activity, onResourceClick, resourceLinkResolver, tenantLinkResolver, tenantRenderer, onActorClick, onActivityClick, isSelected = false, className = "", compact = false, isNew = false, variant = "feed", isLast = false, defaultExpanded = false, }) {
10590
+ function ActivityFeedItem({ activity, onResourceClick, resourceLinkResolver, tenantLinkResolver, tenantRenderer, onActorClick, onActivityClick, isSelected = false, className = "", compact = false, isNew = false, variant = "feed", isLast = false, defaultExpanded = false, density = "default", }) {
10408
10591
  const [isExpanded, setIsExpanded] = React.useState(defaultExpanded);
10409
10592
  const { spec, metadata } = activity;
10410
10593
  const { actor, summary, links, tenant } = spec;
@@ -10425,7 +10608,7 @@ function ActivityFeedItem({ activity, onResourceClick, resourceLinkResolver, ten
10425
10608
  if (isTimeline) {
10426
10609
  const { container: iconBg, icon: iconColor } = getActionIconClasses(verb);
10427
10610
  const Icon = getTimelineIcon(verb);
10428
- return (jsxRuntime.jsxs("div", { className: cn(!isLast && !isExpanded && "border-b border-border", className), children: [jsxRuntime.jsxs("div", { className: cn("flex items-center gap-3 py-3 cursor-pointer group", isSelected && "bg-muted/40"), onClick: toggleExpand, children: [jsxRuntime.jsx("div", { className: cn("w-8 h-8 rounded-md shrink-0 flex items-center justify-center", iconBg, iconColor), children: jsxRuntime.jsx(Icon, { size: 16, strokeWidth: 2 }) }), jsxRuntime.jsx("div", { className: "flex-1 min-w-0 text-sm text-foreground leading-snug", children: jsxRuntime.jsx(ActivityFeedSummary, { summary: summary, links: links, onResourceClick: onResourceClick, resourceLinkResolver: resourceLinkResolver, resourceLinkContext: { tenant } }) }), tenant && (jsxRuntime.jsx("div", { className: "shrink-0", children: tenantRenderer ? (tenantRenderer(tenant)) : (jsxRuntime.jsx(TenantBadge, { tenant: tenant, tenantLinkResolver: tenantLinkResolver, size: "compact" })) })), jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground whitespace-nowrap shrink-0", children: jsxRuntime.jsx(Timestamp, { value: timestamp }) }), jsxRuntime.jsx(button.Button, { type: "quaternary", theme: "borderless", size: "small", className: "h-5 py-0 px-1 text-base text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity shrink-0", onClick: toggleExpand, "aria-expanded": isExpanded, children: isExpanded ? "−" : "+" })] }), isExpanded && (jsxRuntime.jsx(ActivityExpandedDetails, { activity: activity, tenantLinkResolver: tenantLinkResolver, compact: true }))] }));
10611
+ return (jsxRuntime.jsxs("div", { className: cn(!isLast && !isExpanded && "border-b border-border", className), children: [jsxRuntime.jsxs("div", { className: cn("flex items-center gap-3 cursor-pointer group", density === "compact" ? "py-1.5" : "py-3", isSelected && "bg-muted/40"), onClick: toggleExpand, children: [jsxRuntime.jsx("div", { className: cn("rounded-md shrink-0 flex items-center justify-center", density === "compact" ? "w-6 h-6" : "w-8 h-8", iconBg, iconColor), children: jsxRuntime.jsx(Icon, { size: 16, strokeWidth: 2 }) }), jsxRuntime.jsx("div", { className: "flex-1 min-w-0 text-sm text-foreground leading-snug", children: jsxRuntime.jsx(ActivityFeedSummary, { summary: summary, links: links, onResourceClick: onResourceClick, resourceLinkResolver: resourceLinkResolver, resourceLinkContext: { tenant } }) }), tenant && (jsxRuntime.jsx("div", { className: "shrink-0", children: tenantRenderer ? (tenantRenderer(tenant)) : (jsxRuntime.jsx(TenantBadge, { tenant: tenant, tenantLinkResolver: tenantLinkResolver, size: "compact" })) })), jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground whitespace-nowrap shrink-0", children: jsxRuntime.jsx(Timestamp, { value: timestamp }) }), jsxRuntime.jsx(button.Button, { type: "quaternary", theme: "borderless", size: "small", className: "h-5 py-0 px-1 text-base text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity shrink-0", onClick: toggleExpand, "aria-expanded": isExpanded, children: isExpanded ? "−" : "+" })] }), isExpanded && (jsxRuntime.jsx(ActivityExpandedDetails, { activity: activity, tenantLinkResolver: tenantLinkResolver, compact: true }))] }));
10429
10612
  }
10430
10613
  // Feed variant — flat-column table row. Columns: Actor | Summary |
10431
10614
  // Tenant | When | expand toggle. Renders an additional TableRow with a
@@ -10718,7 +10901,7 @@ const getSelectedPreset$1 = (timeRange) => {
10718
10901
  /**
10719
10902
  * ActivityFeedFilters provides filter controls for the activity feed
10720
10903
  */
10721
- function ActivityFeedFilters({ client, filters, timeRange, onFiltersChange, onTimeRangeChange, disabled = false, hiddenFilters = [], className = "", extraFilters, }) {
10904
+ function ActivityFeedFilters({ client, filters, timeRange, onFiltersChange, onTimeRangeChange, disabled = false, hiddenFilters = [], className = "", extraFilters, layout = "inline", }) {
10722
10905
  const { resourceKinds, actorNames, apiGroups, resourceNamespaces, error: facetsError, } = useFacets(client, timeRange, filters);
10723
10906
  // Log facets error for debugging
10724
10907
  if (facetsError) {
@@ -10938,12 +11121,186 @@ function ActivityFeedFilters({ client, filters, timeRange, onFiltersChange, onTi
10938
11121
  clearTimeout(searchDebounceRef.current);
10939
11122
  onFiltersChangeRef.current({ ...filtersRef.current, search: undefined });
10940
11123
  }, []);
11124
+ const searchInput = (jsxRuntime.jsxs("div", { className: "relative min-w-[200px] flex-1 max-w-xs", children: [jsxRuntime.jsx(Search, { className: "absolute left-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }), jsxRuntime.jsx(input.Input, { type: "text", placeholder: "Search activities...", value: searchInputValue, onChange: handleSearchChange, className: "pl-8 h-7 text-xs pr-6" }), searchInputValue && (jsxRuntime.jsx("button", { onClick: handleSearchClear, className: "absolute right-1.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors", "aria-label": "Clear search", children: jsxRuntime.jsx(X, { className: "h-3.5 w-3.5" }) }))] }));
11125
+ const changeSourceControl = !hiddenFilters.includes("changeSource") && (jsxRuntime.jsx(ChangeSourceToggle, { value: filters.changeSource || "all", onChange: handleChangeSourceChange, disabled: disabled }));
11126
+ const filterChips = activeFilterIds.map((filterId) => {
11127
+ const config = FILTER_CONFIGS$1[filterId];
11128
+ return (jsxRuntime.jsx(FilterChip, { label: config.label, values: getFilterValues(filterId), options: config.inputMode === "typeahead"
11129
+ ? getFilterOptions(filterId)
11130
+ : undefined, onValuesChange: (values) => handleFilterChange(filterId, values), onClear: () => handleFilterClear(filterId), onPopoverClose: () => handlePopoverClose(filterId), inputMode: config.inputMode, placeholder: config.placeholder, searchPlaceholder: config.searchPlaceholder, autoOpen: pendingFilter === filterId, disabled: disabled }, filterId));
11131
+ });
11132
+ const addFilterControl = (jsxRuntime.jsx(AddFilterDropdown, { availableFilters: availableFilters, activeFilterIds: activeFilterIds, onAddFilter: handleAddFilter, hasActiveFilters: activeFilterIds.length > 0, disabled: disabled }));
11133
+ const timeRangeControl = (jsxRuntime.jsx(TimeRangeDropdown, { presets: TIME_PRESETS$1, selectedPreset: selectedPreset, onPresetSelect: handleTimePresetSelect, onCustomRangeApply: handleCustomRangeApply, customStart: customStart, customEnd: customEnd, disabled: disabled, displayLabel: getTimeRangeLabel() }));
11134
+ if (layout === "compact") {
11135
+ const activeFilterCount = activeFilterIds.length +
11136
+ (!hiddenFilters.includes("changeSource") &&
11137
+ filters.changeSource &&
11138
+ filters.changeSource !== "all"
11139
+ ? 1
11140
+ : 0);
11141
+ return (jsxRuntime.jsx("div", { className: `border-b border-border py-4 ${className}`, children: jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2 items-center", children: [searchInput, jsxRuntime.jsxs(popover.Popover, { children: [jsxRuntime.jsx(popover.PopoverTrigger, { asChild: true, children: jsxRuntime.jsxs("button", { type: "button", disabled: disabled, className: cn("flex h-7 items-center gap-1.5 rounded-md border border-border bg-background px-2 text-xs", "text-muted-foreground hover:text-foreground hover:border-foreground/50 transition-colors", "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", "disabled:cursor-not-allowed disabled:opacity-50"), children: [jsxRuntime.jsx(SlidersHorizontal, { className: "h-3.5 w-3.5" }), jsxRuntime.jsx("span", { className: "font-medium", children: "Filters" }), activeFilterCount > 0 && (jsxRuntime.jsx("span", { className: "ml-0.5 rounded-full bg-primary text-primary-foreground text-[10px] font-semibold leading-none px-1.5 py-0.5", children: activeFilterCount }))] }) }), jsxRuntime.jsx(popover.PopoverContent, { className: "w-[320px] p-3", sideOffset: 4, align: "start", children: jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [changeSourceControl, jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2 items-center", children: [filterChips, addFilterControl] }), extraFilters, timeRangeControl] }) })] })] }) }));
11142
+ }
10941
11143
  return (jsxRuntime.jsx("div", { className: `border-b border-border py-4 ${className}`, children: jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2 items-center", children: [!hiddenFilters.includes("changeSource") && (jsxRuntime.jsx(ChangeSourceToggle, { value: filters.changeSource || "all", onChange: handleChangeSourceChange, disabled: disabled })), jsxRuntime.jsxs("div", { className: "relative min-w-[200px] flex-1 max-w-xs", children: [jsxRuntime.jsx(Search, { className: "pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }), jsxRuntime.jsx(input.Input, { type: "text", placeholder: "Search activities...", value: searchInputValue, onChange: handleSearchChange, className: "h-9 pl-9 pr-9 text-xs md:text-xs" }), searchInputValue && (jsxRuntime.jsx("button", { onClick: handleSearchClear, className: "absolute right-0 top-0 flex h-9 w-9 items-center justify-center rounded-r-lg text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", "aria-label": "Clear search", children: jsxRuntime.jsx(X, { className: "h-3.5 w-3.5" }) }))] }), activeFilterIds.map((filterId) => {
10942
11144
  const config = FILTER_CONFIGS$1[filterId];
10943
11145
  return (jsxRuntime.jsx(FilterChip, { label: config.label, values: getFilterValues(filterId), options: config.inputMode === "typeahead"
10944
11146
  ? getFilterOptions(filterId)
10945
11147
  : undefined, onValuesChange: (values) => handleFilterChange(filterId, values), onClear: () => handleFilterClear(filterId), onPopoverClose: () => handlePopoverClose(filterId), inputMode: config.inputMode, placeholder: config.placeholder, searchPlaceholder: config.searchPlaceholder, autoOpen: pendingFilter === filterId, disabled: disabled }, filterId));
10946
- }), jsxRuntime.jsx(AddFilterDropdown, { availableFilters: availableFilters, activeFilterIds: activeFilterIds, onAddFilter: handleAddFilter, hasActiveFilters: activeFilterIds.length > 0, disabled: disabled }), extraFilters, jsxRuntime.jsx("div", { className: "flex-1 min-w-[20px]" }), jsxRuntime.jsx(TimeRangeDropdown, { presets: TIME_PRESETS$1, selectedPreset: selectedPreset, onPresetSelect: handleTimePresetSelect, onCustomRangeApply: handleCustomRangeApply, customStart: customStart, customEnd: customEnd, disabled: disabled, displayLabel: getTimeRangeLabel() })] }) }));
11148
+ }), jsxRuntime.jsx(AddFilterDropdown, { availableFilters: availableFilters, activeFilterIds: activeFilterIds, onAddFilter: handleAddFilter, hasActiveFilters: activeFilterIds.length > 0, disabled: disabled }), extraFilters, jsxRuntime.jsx("div", { className: "flex-1 min-w-[20px]" }), timeRangeControl] }) }));
11149
+ }
11150
+
11151
+ const KIND_PLURALS = {
11152
+ DNSRecordSet: "DNS records",
11153
+ };
11154
+ function pluralizeKind(kind, count) {
11155
+ if (count === 1)
11156
+ return kind;
11157
+ return KIND_PLURALS[kind] ?? `${kind}s`;
11158
+ }
11159
+ const VERB_LABEL = {
11160
+ create: "created",
11161
+ update: "updated",
11162
+ delete: "deleted",
11163
+ other: "changed",
11164
+ };
11165
+ /**
11166
+ * ActivityFeedGroupItem renders a run of consecutive same verb/kind/actor
11167
+ * activities (see `groupActivities`) as a single collapsed row in the
11168
+ * "digest" timeline. A group of size 1 renders exactly like a normal
11169
+ * `ActivityFeedItem` — no visual change for ungrouped activities. Larger
11170
+ * groups render a synthesized summary ("{actor} created 12 DNS records")
11171
+ * that expands to reveal the individual activities underneath.
11172
+ */
11173
+ function ActivityFeedGroupItem({ group, resourceLinkResolver, tenantLinkResolver, tenantRenderer, onActorClick, onActivityClick, isLast = false, }) {
11174
+ const [isExpanded, setIsExpanded] = React.useState(false);
11175
+ if (group.count === 1) {
11176
+ return (jsxRuntime.jsx(ActivityFeedItem, { activity: group.activities[0], resourceLinkResolver: resourceLinkResolver, tenantLinkResolver: tenantLinkResolver, tenantRenderer: tenantRenderer, onActorClick: onActorClick, onActivityClick: onActivityClick, variant: "timeline", density: "compact", isLast: isLast }));
11177
+ }
11178
+ const { container: iconBg, icon: iconColor } = getActionIconClasses(group.verb);
11179
+ const Icon = getTimelineIcon(group.verb);
11180
+ const actorVisible = group.actorDisplayName || group.actorName;
11181
+ const summary = `${actorVisible} ${VERB_LABEL[group.verb]} ${group.count} ${pluralizeKind(group.kind, group.count)}`;
11182
+ const toggleExpand = (e) => {
11183
+ e.stopPropagation();
11184
+ setIsExpanded((prev) => !prev);
11185
+ };
11186
+ return (jsxRuntime.jsxs("div", { className: cn(!isLast && !isExpanded && "border-b border-border"), children: [jsxRuntime.jsxs("div", { className: "flex items-center gap-3 py-1.5 cursor-pointer group", onClick: toggleExpand, children: [jsxRuntime.jsxs("div", { className: cn("w-6 h-6 rounded-md shrink-0 flex items-center justify-center relative", iconBg, iconColor), children: [jsxRuntime.jsx(Icon, { size: 14, strokeWidth: 2 }), jsxRuntime.jsx("span", { className: "absolute -bottom-1 -right-1 rounded-full bg-background border border-border px-1 text-[10px] font-semibold leading-tight text-foreground", children: group.count })] }), jsxRuntime.jsx("div", { className: "flex-1 min-w-0 text-xs text-foreground leading-normal", children: summary }), group.tenant && (jsxRuntime.jsx("div", { className: "shrink-0", children: tenantRenderer ? (tenantRenderer(group.tenant)) : (jsxRuntime.jsx(TenantBadge, { tenant: group.tenant, tenantLinkResolver: tenantLinkResolver, size: "compact" })) })), jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground whitespace-nowrap shrink-0", children: jsxRuntime.jsx(Timestamp, { value: group.latestTimestamp }) }), jsxRuntime.jsx(button.Button, { type: "quaternary", theme: "borderless", size: "small", className: "h-5 py-0 px-1 text-base text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity shrink-0", onClick: toggleExpand, "aria-expanded": isExpanded, children: isExpanded ? "−" : "+" })] }), isExpanded && (jsxRuntime.jsx("div", { className: "pl-11 flex flex-col", children: group.activities.map((activity, index) => (jsxRuntime.jsx(ActivityFeedItem, { activity: activity, resourceLinkResolver: resourceLinkResolver, tenantLinkResolver: tenantLinkResolver, tenantRenderer: tenantRenderer, onActorClick: onActorClick, onActivityClick: onActivityClick, compact: true, density: "compact", variant: "timeline", isLast: index === group.activities.length - 1 }, activity.metadata?.uid || activity.metadata?.name))) }))] }));
11187
+ }
11188
+
11189
+ /**
11190
+ * Thin stat strip summarizing the activities currently loaded in the feed —
11191
+ * e.g. "18 changes · 3 people · 1 system". Computed client-side over the
11192
+ * loaded/paginated activities; it is a quick-glance headline, not a
11193
+ * full-range aggregate (that would require a backend facet query).
11194
+ */
11195
+ function ActivityDigestSummary({ activities, className = "" }) {
11196
+ const stats = React.useMemo(() => {
11197
+ const humanActors = new Set();
11198
+ const systemActors = new Set();
11199
+ for (const activity of activities) {
11200
+ const { actor, changeSource } = activity.spec;
11201
+ if (changeSource === "human") {
11202
+ humanActors.add(actor.name);
11203
+ }
11204
+ else {
11205
+ systemActors.add(actor.name);
11206
+ }
11207
+ }
11208
+ return {
11209
+ total: activities.length,
11210
+ people: humanActors.size,
11211
+ systems: systemActors.size,
11212
+ };
11213
+ }, [activities]);
11214
+ if (stats.total === 0)
11215
+ return null;
11216
+ const parts = [`${stats.total} ${stats.total === 1 ? "change" : "changes"}`];
11217
+ if (stats.people > 0)
11218
+ parts.push(`${stats.people} ${stats.people === 1 ? "person" : "people"}`);
11219
+ if (stats.systems > 0)
11220
+ parts.push(`${stats.systems} ${stats.systems === 1 ? "system" : "systems"}`);
11221
+ return (jsxRuntime.jsx("div", { className: `px-1 pt-4 pb-2 text-xs text-muted-foreground ${className}`, children: parts.join(" · ") }));
11222
+ }
11223
+
11224
+ function tenantKey(tenant) {
11225
+ if (!tenant)
11226
+ return '';
11227
+ return `${tenant.type}:${tenant.name}`;
11228
+ }
11229
+ function groupSignature(activity) {
11230
+ const { spec } = activity;
11231
+ const verb = normalizeVerb(extractVerb(spec.summary));
11232
+ return [verb, spec.resource.kind, spec.actor.name, tenantKey(spec.tenant)].join('|');
11233
+ }
11234
+ /**
11235
+ * Group consecutive activities that share verb + resource kind + actor +
11236
+ * tenant. Activities are assumed to already be sorted newest-first (as
11237
+ * returned by `useActivityFeed`); only adjacent items are merged, so a
11238
+ * burst of matching activities collapses into one group without needing
11239
+ * any time-window heuristics.
11240
+ */
11241
+ function groupActivities(activities) {
11242
+ const groups = [];
11243
+ for (const activity of activities) {
11244
+ const signature = groupSignature(activity);
11245
+ const last = groups[groups.length - 1];
11246
+ if (last && last.key === signature) {
11247
+ last.activities.push(activity);
11248
+ last.count += 1;
11249
+ continue;
11250
+ }
11251
+ const { spec } = activity;
11252
+ groups.push({
11253
+ key: signature,
11254
+ verb: normalizeVerb(extractVerb(spec.summary)),
11255
+ kind: spec.resource.kind,
11256
+ actorName: spec.actor.name,
11257
+ actorDisplayName: spec.actor.displayName,
11258
+ tenant: spec.tenant,
11259
+ activities: [activity],
11260
+ count: 1,
11261
+ latestTimestamp: activity.metadata?.creationTimestamp,
11262
+ });
11263
+ }
11264
+ return groups;
11265
+ }
11266
+ function dayLabel(timestamp) {
11267
+ if (!timestamp)
11268
+ return 'Unknown date';
11269
+ const date = new Date(timestamp);
11270
+ if (Number.isNaN(date.getTime()))
11271
+ return 'Unknown date';
11272
+ if (isToday(date))
11273
+ return 'Today';
11274
+ if (isYesterday(date))
11275
+ return 'Yesterday';
11276
+ return format(date, 'MMMM d, yyyy');
11277
+ }
11278
+ /**
11279
+ * Bucket activities into calendar-day sections (Today / Yesterday / a
11280
+ * formatted date), then group consecutive matching activities within each
11281
+ * day. Bucketing by day first (rather than grouping across the whole list)
11282
+ * keeps a group from silently spanning a day boundary.
11283
+ */
11284
+ function groupActivitiesByDay(activities) {
11285
+ const sections = [];
11286
+ let currentLabel = null;
11287
+ let currentBucket = [];
11288
+ const flush = () => {
11289
+ if (currentLabel !== null && currentBucket.length > 0) {
11290
+ sections.push({ label: currentLabel, groups: groupActivities(currentBucket) });
11291
+ }
11292
+ };
11293
+ for (const activity of activities) {
11294
+ const label = dayLabel(activity.metadata?.creationTimestamp);
11295
+ if (label !== currentLabel) {
11296
+ flush();
11297
+ currentLabel = label;
11298
+ currentBucket = [];
11299
+ }
11300
+ currentBucket.push(activity);
11301
+ }
11302
+ flush();
11303
+ return sections;
10947
11304
  }
10948
11305
 
10949
11306
  /**
@@ -11074,10 +11431,10 @@ function ActivityFeed({ client, initialFilters = { changeSource: "human" }, init
11074
11431
  // Parent containers must have proper height constraints (h-screen/h-full + overflow-hidden)
11075
11432
  const effectiveMaxHeight = maxHeight === "none" ? undefined : maxHeight;
11076
11433
  const listClasses = "flex-1 min-h-0 overflow-y-auto flex flex-col";
11077
- return (jsxRuntime.jsx(TooltipProvider, { delayDuration: 200, children: jsxRuntime.jsxs(card.Card, { className: containerClasses, children: [enableStreaming && (jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [jsxRuntime.jsxs("div", { className: "flex items-center", children: [isStreaming && !watchError && (jsxRuntime.jsx(TooltipProvider, { delayDuration: 300, children: jsxRuntime.jsxs(Tooltip, { children: [jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsxs("span", { className: "relative flex h-2 w-2", children: [jsxRuntime.jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-[var(--success-500)] opacity-75" }), jsxRuntime.jsx("span", { className: "relative inline-flex rounded-full h-2 w-2 bg-[var(--success-500)]" })] }), jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "Streaming activity..." })] }) }), jsxRuntime.jsx(TooltipContent, { className: "text-xs", children: jsxRuntime.jsx("p", { children: "New activities will appear automatically" }) })] }) })), watchError && (jsxRuntime.jsx(TooltipProvider, { delayDuration: 300, children: jsxRuntime.jsxs(Tooltip, { children: [jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsx("span", { className: "relative flex h-2 w-2", children: jsxRuntime.jsx("span", { className: "relative inline-flex rounded-full h-2 w-2 bg-destructive" }) }), jsxRuntime.jsx("span", { className: "text-xs text-destructive", children: "Connection error" })] }) }), jsxRuntime.jsx(TooltipContent, { className: "text-xs", children: jsxRuntime.jsx("p", { children: "Stream connection lost" }) })] }) })), newActivitiesCount > 0 && !watchError && (jsxRuntime.jsxs(Badge, { variant: "secondary", className: "text-xs", children: ["+", newActivitiesCount, " new"] }))] }), jsxRuntime.jsx(button.Button, { type: "tertiary", theme: "outline", size: "small", onClick: handleStreamingToggle, children: watchError ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("svg", { className: "w-4 h-4 mr-1.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", strokeWidth: "2", children: [jsxRuntime.jsx("path", { d: "M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4" }), jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "3" })] }), "Retry"] })) : isStreaming ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("svg", { className: "w-4 h-4 mr-1.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: [jsxRuntime.jsx("rect", { x: "6", y: "4", width: "4", height: "16" }), jsxRuntime.jsx("rect", { x: "14", y: "4", width: "4", height: "16" })] }), "Pause"] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("svg", { className: "w-4 h-4 mr-1.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsxRuntime.jsx("polygon", { points: "5,3 19,12 5,21", fill: "currentColor" }) }), "Resume"] })) })] })), showFilters && (jsxRuntime.jsx(ActivityFeedFilters, { client: client, filters: filters, timeRange: timeRange, onFiltersChange: handleFiltersChange, onTimeRangeChange: handleTimeRangeChange, disabled: isLoading, hiddenFilters: hiddenFilters })), jsxRuntime.jsx(ApiErrorAlert, { error: error, onRetry: refresh, className: "mb-4", errorFormatter: errorFormatter }), jsxRuntime.jsx(ApiErrorAlert, { error: watchError, onRetry: startStreaming, className: "mb-4", errorFormatter: errorFormatter }), !policiesLoading && hasPolicies === false && (jsxRuntime.jsxs("div", { className: "flex flex-col items-center py-12 px-8 text-center bg-muted border border-dashed border-border rounded-xl mb-4", children: [jsxRuntime.jsx("div", { className: "flex justify-center mb-4 text-muted-foreground", children: jsxRuntime.jsxs("svg", { width: "56", height: "56", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("path", { d: "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2" }), jsxRuntime.jsx("rect", { x: "9", y: "3", width: "6", height: "4", rx: "1" }), jsxRuntime.jsx("path", { d: "M9 12h6" }), jsxRuntime.jsx("path", { d: "M9 16h6" })] }) }), jsxRuntime.jsx("h3", { className: "m-0 mb-2 text-lg font-semibold text-foreground leading-snug", children: "Get started with activity logging" }), jsxRuntime.jsx("p", { className: "m-0 mb-6 text-sm leading-relaxed text-muted-foreground max-w-[400px]", children: "Activity policies define which resources to track and how to summarize changes. Create your first policy to start seeing activity logs here." }), onCreatePolicy && (jsxRuntime.jsx(button.Button, { onClick: onCreatePolicy, children: "Create Policy" }))] })), jsxRuntime.jsxs("div", { className: listClasses, ref: scrollContainerRef, style: effectiveMaxHeight ? { maxHeight: effectiveMaxHeight } : undefined, children: [!isLoading &&
11434
+ return (jsxRuntime.jsx(TooltipProvider, { delayDuration: 200, children: jsxRuntime.jsxs(card.Card, { className: containerClasses, children: [enableStreaming && (jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [jsxRuntime.jsxs("div", { className: "flex items-center", children: [isStreaming && !watchError && (jsxRuntime.jsx(TooltipProvider, { delayDuration: 300, children: jsxRuntime.jsxs(Tooltip, { children: [jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsxs("span", { className: "relative flex h-2 w-2", children: [jsxRuntime.jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-[var(--success-500)] opacity-75" }), jsxRuntime.jsx("span", { className: "relative inline-flex rounded-full h-2 w-2 bg-[var(--success-500)]" })] }), jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "Streaming activity..." })] }) }), jsxRuntime.jsx(TooltipContent, { className: "text-xs", children: jsxRuntime.jsx("p", { children: "New activities will appear automatically" }) })] }) })), watchError && (jsxRuntime.jsx(TooltipProvider, { delayDuration: 300, children: jsxRuntime.jsxs(Tooltip, { children: [jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsx("span", { className: "relative flex h-2 w-2", children: jsxRuntime.jsx("span", { className: "relative inline-flex rounded-full h-2 w-2 bg-destructive" }) }), jsxRuntime.jsx("span", { className: "text-xs text-destructive", children: "Connection error" })] }) }), jsxRuntime.jsx(TooltipContent, { className: "text-xs", children: jsxRuntime.jsx("p", { children: "Stream connection lost" }) })] }) })), newActivitiesCount > 0 && !watchError && (jsxRuntime.jsxs(Badge, { variant: "secondary", className: "text-xs", children: ["+", newActivitiesCount, " new"] }))] }), jsxRuntime.jsx(button.Button, { type: "tertiary", theme: "outline", size: "small", onClick: handleStreamingToggle, children: watchError ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("svg", { className: "w-4 h-4 mr-1.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", strokeWidth: "2", children: [jsxRuntime.jsx("path", { d: "M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4" }), jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "3" })] }), "Retry"] })) : isStreaming ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("svg", { className: "w-4 h-4 mr-1.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: [jsxRuntime.jsx("rect", { x: "6", y: "4", width: "4", height: "16" }), jsxRuntime.jsx("rect", { x: "14", y: "4", width: "4", height: "16" })] }), "Pause"] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("svg", { className: "w-4 h-4 mr-1.5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsxRuntime.jsx("polygon", { points: "5,3 19,12 5,21", fill: "currentColor" }) }), "Resume"] })) })] })), showFilters && (jsxRuntime.jsx(ActivityFeedFilters, { client: client, filters: filters, timeRange: timeRange, onFiltersChange: handleFiltersChange, onTimeRangeChange: handleTimeRangeChange, disabled: isLoading, hiddenFilters: hiddenFilters, layout: variant === "digest" ? "compact" : "inline" })), variant === "digest" && !isLoading && activities.length > 0 && (jsxRuntime.jsx(ActivityDigestSummary, { activities: activities })), jsxRuntime.jsx(ApiErrorAlert, { error: error, onRetry: refresh, className: "mb-4", errorFormatter: errorFormatter }), jsxRuntime.jsx(ApiErrorAlert, { error: watchError, onRetry: startStreaming, className: "mb-4", errorFormatter: errorFormatter }), !policiesLoading && hasPolicies === false && (jsxRuntime.jsxs("div", { className: "flex flex-col items-center py-12 px-8 text-center bg-muted border border-dashed border-border rounded-xl mb-4", children: [jsxRuntime.jsx("div", { className: "flex justify-center mb-4 text-muted-foreground", children: jsxRuntime.jsxs("svg", { width: "56", height: "56", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [jsxRuntime.jsx("path", { d: "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2" }), jsxRuntime.jsx("rect", { x: "9", y: "3", width: "6", height: "4", rx: "1" }), jsxRuntime.jsx("path", { d: "M9 12h6" }), jsxRuntime.jsx("path", { d: "M9 16h6" })] }) }), jsxRuntime.jsx("h3", { className: "m-0 mb-2 text-lg font-semibold text-foreground leading-snug", children: "Get started with activity logging" }), jsxRuntime.jsx("p", { className: "m-0 mb-6 text-sm leading-relaxed text-muted-foreground max-w-[400px]", children: "Activity policies define which resources to track and how to summarize changes. Create your first policy to start seeing activity logs here." }), onCreatePolicy && (jsxRuntime.jsx(button.Button, { onClick: onCreatePolicy, children: "Create Policy" }))] })), jsxRuntime.jsxs("div", { className: listClasses, ref: scrollContainerRef, style: effectiveMaxHeight ? { maxHeight: effectiveMaxHeight } : undefined, children: [!isLoading &&
11078
11435
  activities.length === 0 &&
11079
11436
  hasPolicies !== false &&
11080
- variant === "timeline" && (jsxRuntime.jsxs("div", { className: "py-12 text-center text-muted-foreground", children: [jsxRuntime.jsx("p", { className: "m-0", children: "No activities found" }), jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground mt-2 m-0", children: "Try adjusting your filters or time range" })] })), variant === "timeline" ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [isLoading && activities.length === 0 && (jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 8 }).map((_, index) => (jsxRuntime.jsx(ActivityFeedItemSkeleton, { compact: compact }, index))) })), activities.map((activity, index) => (jsxRuntime.jsx(ActivityFeedItem, { activity: activity, onResourceClick: onResourceClick, resourceLinkResolver: resourceLinkResolver, tenantLinkResolver: tenantLinkResolver, tenantRenderer: tenantRenderer, onActorClick: handleActorClick, onActivityClick: onActivityClick, compact: compact, isNew: enableStreaming && index < newActivitiesCount, variant: variant, isLast: index === activities.length - 1 }, activity.metadata?.uid || activity.metadata?.name)))] })) : (jsxRuntime.jsxs(table.Table, { className: "w-full", children: [jsxRuntime.jsx(table.TableHeader, { children: jsxRuntime.jsxs(table.TableRow, { children: [jsxRuntime.jsx(table.TableHead, { className: "w-12", "aria-label": "Actor" }), jsxRuntime.jsx(table.TableHead, { children: "Summary" }), jsxRuntime.jsx(table.TableHead, { children: "Tenant" }), jsxRuntime.jsx(table.TableHead, { className: "w-[170px]", children: "When" }), jsxRuntime.jsx(table.TableHead, { className: "w-10", "aria-label": "Expand" })] }) }), jsxRuntime.jsxs(table.TableBody, { children: [isLoading && activities.length === 0 && (jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 8 }).map((_, index) => (jsxRuntime.jsxs(table.TableRow, { children: [jsxRuntime.jsx(table.TableCell, { className: "py-2", children: jsxRuntime.jsx(skeleton.Skeleton, { className: "h-6 w-6 rounded-full" }) }), jsxRuntime.jsx(table.TableCell, { className: "py-2", children: jsxRuntime.jsx(skeleton.Skeleton, { className: "h-4 w-3/4" }) }), jsxRuntime.jsx(table.TableCell, { className: "py-2", children: jsxRuntime.jsx(skeleton.Skeleton, { className: "h-5 w-32 rounded-full" }) }), jsxRuntime.jsx(table.TableCell, { className: "py-2", children: jsxRuntime.jsx(skeleton.Skeleton, { className: "h-4 w-24" }) }), jsxRuntime.jsx(table.TableCell, { className: "py-2", children: jsxRuntime.jsx(skeleton.Skeleton, { className: "h-6 w-6" }) })] }, `sk-${index}`))) })), !isLoading &&
11437
+ (variant === "timeline" || variant === "digest") && (jsxRuntime.jsxs("div", { className: "py-12 text-center text-muted-foreground", children: [jsxRuntime.jsx("p", { className: "m-0", children: "No activities found" }), jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground mt-2 m-0", children: "Try adjusting your filters or time range" })] })), variant === "digest" ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [isLoading && activities.length === 0 && (jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 8 }).map((_, index) => (jsxRuntime.jsx(ActivityFeedItemSkeleton, { compact: compact }, index))) })), groupActivitiesByDay(activities).map((section) => (jsxRuntime.jsxs("div", { className: "flex flex-col", children: [jsxRuntime.jsx("div", { className: "sticky top-0 z-10 bg-card pt-2 pb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: section.label }), section.groups.map((group, index) => (jsxRuntime.jsx(ActivityFeedGroupItem, { group: group, resourceLinkResolver: resourceLinkResolver, tenantLinkResolver: tenantLinkResolver, tenantRenderer: tenantRenderer, onActorClick: handleActorClick, onActivityClick: onActivityClick, isLast: index === section.groups.length - 1 }, `${section.label}-${group.key}-${index}`)))] }, section.label)))] })) : variant === "timeline" ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [isLoading && activities.length === 0 && (jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 8 }).map((_, index) => (jsxRuntime.jsx(ActivityFeedItemSkeleton, { compact: compact }, index))) })), activities.map((activity, index) => (jsxRuntime.jsx(ActivityFeedItem, { activity: activity, onResourceClick: onResourceClick, resourceLinkResolver: resourceLinkResolver, tenantLinkResolver: tenantLinkResolver, tenantRenderer: tenantRenderer, onActorClick: handleActorClick, onActivityClick: onActivityClick, compact: compact, isNew: enableStreaming && index < newActivitiesCount, variant: variant, isLast: index === activities.length - 1 }, activity.metadata?.uid || activity.metadata?.name)))] })) : (jsxRuntime.jsxs(table.Table, { className: "w-full", children: [jsxRuntime.jsx(table.TableHeader, { children: jsxRuntime.jsxs(table.TableRow, { children: [jsxRuntime.jsx(table.TableHead, { className: "w-12", "aria-label": "Actor" }), jsxRuntime.jsx(table.TableHead, { children: "Summary" }), jsxRuntime.jsx(table.TableHead, { children: "Tenant" }), jsxRuntime.jsx(table.TableHead, { className: "w-[170px]", children: "When" }), jsxRuntime.jsx(table.TableHead, { className: "w-10", "aria-label": "Expand" })] }) }), jsxRuntime.jsxs(table.TableBody, { children: [isLoading && activities.length === 0 && (jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 8 }).map((_, index) => (jsxRuntime.jsxs(table.TableRow, { children: [jsxRuntime.jsx(table.TableCell, { className: "py-2", children: jsxRuntime.jsx(skeleton.Skeleton, { className: "h-6 w-6 rounded-full" }) }), jsxRuntime.jsx(table.TableCell, { className: "py-2", children: jsxRuntime.jsx(skeleton.Skeleton, { className: "h-4 w-3/4" }) }), jsxRuntime.jsx(table.TableCell, { className: "py-2", children: jsxRuntime.jsx(skeleton.Skeleton, { className: "h-5 w-32 rounded-full" }) }), jsxRuntime.jsx(table.TableCell, { className: "py-2", children: jsxRuntime.jsx(skeleton.Skeleton, { className: "h-4 w-24" }) }), jsxRuntime.jsx(table.TableCell, { className: "py-2", children: jsxRuntime.jsx(skeleton.Skeleton, { className: "h-6 w-6" }) })] }, `sk-${index}`))) })), !isLoading &&
11081
11438
  activities.length === 0 &&
11082
11439
  hasPolicies !== false && (jsxRuntime.jsx(table.TableRow, { children: jsxRuntime.jsxs(table.TableCell, { colSpan: ACTIVITY_FEED_COLUMN_COUNT, className: "text-center py-12 text-muted-foreground", children: [jsxRuntime.jsx("div", { children: "No activities found" }), jsxRuntime.jsx("div", { className: "text-sm mt-2", children: "Try adjusting your filters or time range" })] }) })), activities.map((activity, index) => (jsxRuntime.jsx(ActivityFeedItem, { activity: activity, onResourceClick: onResourceClick, resourceLinkResolver: resourceLinkResolver, tenantLinkResolver: tenantLinkResolver, tenantRenderer: tenantRenderer, onActorClick: handleActorClick, onActivityClick: onActivityClick, compact: compact, isNew: enableStreaming && index < newActivitiesCount, variant: variant, isLast: index === activities.length - 1 }, activity.metadata?.uid || activity.metadata?.name)))] })] })), infiniteScroll && hasMore && (jsxRuntime.jsx("div", { ref: loadMoreTriggerRef, className: "h-px mt-4" })), !infiniteScroll && activities.length > 0 ? (jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-4 px-4 py-3 border-t border-border text-sm text-muted-foreground", children: [jsxRuntime.jsxs("span", { children: [activities.length, " ", activities.length === 1 ? "activity" : "activities", hasMore ? " so far" : ""] }), hasMore ? (jsxRuntime.jsx(button.Button, { type: "tertiary", theme: "outline", size: "small", htmlType: "button", onClick: handleLoadMoreClick, disabled: isLoading, children: isLoading ? "Loading…" : "Load more" })) : (jsxRuntime.jsx("span", { children: "End of results" }))] })) : null] })] }) }));
11083
11440
  }
@@ -15022,7 +15379,9 @@ function serializeActivityFilters(filters, timeRange, streamingEnabled = true) {
15022
15379
  if (!streamingEnabled) {
15023
15380
  params.set('streaming', 'false');
15024
15381
  }
15025
- if (filters.changeSource && filters.changeSource !== 'human') {
15382
+ // Written even when it matches a default: the default differs between
15383
+ // pages, so an omitted value can't be read back as the one chosen.
15384
+ if (filters.changeSource) {
15026
15385
  params.set('changeSource', filters.changeSource);
15027
15386
  }
15028
15387
  if (filters.actorNames && filters.actorNames.length > 0) {
@@ -15086,12 +15445,11 @@ function serializeEventFilters(filters, timeRange, streamingEnabled = true) {
15086
15445
  function parseActivityFilters(searchParams) {
15087
15446
  const filters = {};
15088
15447
  const changeSource = searchParams.get('changeSource');
15448
+ // Left unset when absent so the caller's default applies. Filling in
15449
+ // 'human' here overrode pages that default to 'all'.
15089
15450
  if (changeSource === 'human' || changeSource === 'system' || changeSource === 'all') {
15090
15451
  filters.changeSource = changeSource;
15091
15452
  }
15092
- else {
15093
- filters.changeSource = 'human';
15094
- }
15095
15453
  const actorNames = searchParams.get('actorNames');
15096
15454
  if (actorNames) {
15097
15455
  filters.actorNames = actorNames.split(',').filter(Boolean);
@@ -16587,9 +16945,11 @@ exports.AUDIT_TEMPLATES = AUDIT_TEMPLATES;
16587
16945
  exports.ActionMultiSelect = ActionMultiSelect;
16588
16946
  exports.ActionToggle = ActionToggle;
16589
16947
  exports.ActivityApiClient = ActivityApiClient;
16948
+ exports.ActivityDigestSummary = ActivityDigestSummary;
16590
16949
  exports.ActivityExpandedDetails = ActivityExpandedDetails;
16591
16950
  exports.ActivityFeed = ActivityFeed;
16592
16951
  exports.ActivityFeedFilters = ActivityFeedFilters;
16952
+ exports.ActivityFeedGroupItem = ActivityFeedGroupItem;
16593
16953
  exports.ActivityFeedItem = ActivityFeedItem;
16594
16954
  exports.ActivityFeedItemSkeleton = ActivityFeedItemSkeleton;
16595
16955
  exports.ActivityFeedSummary = ActivityFeedSummary;
@@ -16669,6 +17029,8 @@ exports.extractFieldPathsFromMany = extractFieldPathsFromMany;
16669
17029
  exports.generateShareableUrl = generateShareableUrl;
16670
17030
  exports.getReindexJobDuration = getReindexJobDuration;
16671
17031
  exports.getReindexJobStatusMessage = getReindexJobStatusMessage;
17032
+ exports.groupActivities = groupActivities;
17033
+ exports.groupActivitiesByDay = groupActivitiesByDay;
16672
17034
  exports.isEventRecord = isEventRecord;
16673
17035
  exports.isReindexJobRunning = isReindexJobRunning;
16674
17036
  exports.isReindexJobTerminal = isReindexJobTerminal;