@syncended/dsh-automations 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/lib/client.js +521 -32
  3. package/package.json +4 -2
package/README.md CHANGED
@@ -56,7 +56,7 @@ dsh plugin --profile web remove @syncended/dsh-automations
56
56
  |---|---|
57
57
  | Name | Human-readable job name. |
58
58
  | Cron | Five fields: `minute hour day-of-month month day-of-week`. Example: `0 9 * * 1-5`. |
59
- | Timezone | `UTC` or an IANA name such as `Europe/Berlin`. DST is handled by `cron-parser`. |
59
+ | Time zone | Search by city or region in the picker, or enter `UTC` / an IANA name such as `Europe/Berlin`. Current UTC offsets are shown; DST is handled by `cron-parser`. |
60
60
  | Project | Existing absolute directory. Its canonical filesystem identity becomes the Session cwd and `workspace-write` root. |
61
61
  | Prompt | The user message sent to a fresh Harness Agent. |
62
62
  | Provider / model | Leave both blank to resolve the current Harness default at dispatch time. Set both to pin a route. |
package/lib/client.js CHANGED
@@ -11,6 +11,7 @@ window.__ModuleLoader__.load({
11
11
  // the same-origin HTTP API at /api/automations; every mutation carries the
12
12
  // x-dsh-automation-client fence header plus an application/json body.
13
13
  const React = require("react");
14
+ const { createPortal } = require("react-dom");
14
15
  const h = React.createElement;
15
16
  const { useCallback, useEffect, useId, useRef, useState, useSyncExternalStore } = React;
16
17
  const {
@@ -23,9 +24,11 @@ window.__ModuleLoader__.load({
23
24
  IconChevronDownOutline14,
24
25
  IconChevronLeftOutline14,
25
26
  IconEditOutline16,
27
+ IconGlobeOutline14,
26
28
  IconPlayOutline16,
27
29
  IconPlusOutline16,
28
30
  IconRefreshOutline16,
31
+ IconSearchOutline16,
29
32
  IconSettingsOutline16,
30
33
  IconTrashOutline16,
31
34
  IconWarningOutline16,
@@ -50,24 +53,75 @@ window.__ModuleLoader__.load({
50
53
  }
51
54
  })();
52
55
 
53
- const TIMEZONES = Array.from(
54
- new Set([
55
- BROWSER_TIMEZONE,
56
- "UTC",
57
- "America/New_York",
58
- "America/Chicago",
59
- "America/Denver",
60
- "America/Los_Angeles",
61
- "Europe/London",
62
- "Europe/Paris",
63
- "Europe/Berlin",
64
- "Asia/Shanghai",
65
- "Asia/Tokyo",
66
- "Asia/Singapore",
67
- "Asia/Kolkata",
68
- "Australia/Sydney",
69
- ]),
56
+ const POPULAR_TIMEZONE_CANDIDATES = [
57
+ BROWSER_TIMEZONE,
58
+ "UTC",
59
+ "America/New_York",
60
+ "America/Los_Angeles",
61
+ "America/Sao_Paulo",
62
+ "Europe/London",
63
+ "Europe/Berlin",
64
+ "Europe/Moscow",
65
+ "Asia/Dubai",
66
+ "Asia/Kolkata",
67
+ "Asia/Shanghai",
68
+ "Asia/Tokyo",
69
+ "Australia/Sydney",
70
+ ];
71
+ const PREFERRED_TIMEZONE_ALIASES = [
72
+ "Africa/Asmara",
73
+ "America/Argentina/Buenos_Aires",
74
+ "America/Atikokan",
75
+ "America/Indiana/Indianapolis",
76
+ "America/Kentucky/Louisville",
77
+ "America/Nuuk",
78
+ "Asia/Ho_Chi_Minh",
79
+ "Asia/Kathmandu",
80
+ "Asia/Kolkata",
81
+ "Asia/Yangon",
82
+ "Atlantic/Faroe",
83
+ "Europe/Kyiv",
84
+ "Pacific/Chuuk",
85
+ "Pacific/Kanton",
86
+ "Pacific/Pohnpei",
87
+ ];
88
+ const TIMEZONE_ALIASES_BY_ZONE = new Map();
89
+ for (const alias of PREFERRED_TIMEZONE_ALIASES) {
90
+ const zone = canonicalTimezone(alias);
91
+ if (!zone || zone === alias) continue;
92
+ const aliases = TIMEZONE_ALIASES_BY_ZONE.get(zone) || [];
93
+ aliases.push(alias);
94
+ TIMEZONE_ALIASES_BY_ZONE.set(zone, aliases);
95
+ }
96
+ const POPULAR_TIMEZONES = Array.from(
97
+ new Set(POPULAR_TIMEZONE_CANDIDATES.map(canonicalTimezone).filter(Boolean)),
70
98
  );
99
+ const SUPPORTED_TIMEZONES = (() => {
100
+ let discovered = [];
101
+ try {
102
+ discovered = typeof Intl.supportedValuesOf === "function"
103
+ ? Intl.supportedValuesOf("timeZone")
104
+ : [];
105
+ } catch {
106
+ discovered = [];
107
+ }
108
+ return Array.from(
109
+ new Set([
110
+ ...POPULAR_TIMEZONES,
111
+ ...TIMEZONE_ALIASES_BY_ZONE.keys(),
112
+ ...discovered.filter((zone) => typeof zone === "string" && zone !== ""),
113
+ ]),
114
+ ).sort((left, right) => left.localeCompare(right));
115
+ })();
116
+ const TIMEZONE_SEARCH_INDEX = SUPPORTED_TIMEZONES.map((zone) => {
117
+ const aliases = TIMEZONE_ALIASES_BY_ZONE.get(zone) || [];
118
+ const preferred = aliases[0] || zone;
119
+ return {
120
+ zone,
121
+ city: timezoneCityLabel(preferred),
122
+ search: timezoneSearchKey([zone, ...aliases].join(" ")),
123
+ };
124
+ });
71
125
 
72
126
  const REASONING_EFFORTS = ["low", "medium", "high"];
73
127
  const OVERLAP_OPTIONS = [
@@ -147,6 +201,110 @@ window.__ModuleLoader__.load({
147
201
  });
148
202
  }
149
203
 
204
+ function canonicalTimezone(value) {
205
+ const timezone = String(value || "").trim();
206
+ if (timezone === "") return null;
207
+ try {
208
+ return new Intl.DateTimeFormat("en-US", { timeZone: timezone }).resolvedOptions().timeZone;
209
+ } catch {
210
+ return null;
211
+ }
212
+ }
213
+
214
+ function timezoneCityLabel(zone) {
215
+ if (zone === "UTC") return "UTC";
216
+ const segments = zone.split("/");
217
+ return (segments[segments.length - 1] || zone).replace(/_/g, " ");
218
+ }
219
+
220
+ function timezonePreferredValue(zone) {
221
+ return TIMEZONE_ALIASES_BY_ZONE.get(zone)?.[0] || zone;
222
+ }
223
+
224
+ function timezoneSearchKey(zone) {
225
+ const literal = zone.toLowerCase();
226
+ const expanded = literal
227
+ .normalize("NFKD")
228
+ .replace(/[\u0300-\u036f]/g, "")
229
+ .replace(/[\/_\-.]+/g, " ")
230
+ .replace(/\s+/g, " ")
231
+ .trim();
232
+ return expanded;
233
+ }
234
+
235
+ function timezoneOffsetLabel(zone, at = new Date()) {
236
+ try {
237
+ const formatter = new Intl.DateTimeFormat("en-US", {
238
+ timeZone: zone,
239
+ timeZoneName: "longOffset",
240
+ hour: "2-digit",
241
+ });
242
+ const value = formatter.formatToParts(at).find((part) => part.type === "timeZoneName")?.value;
243
+ if (!value) return "UTC offset unavailable";
244
+ if (value === "GMT") return "UTC+00:00";
245
+ return value.replace(/^GMT/, "UTC");
246
+ } catch {
247
+ return "UTC offset unavailable";
248
+ }
249
+ }
250
+
251
+ function timezoneSearchResults(query, currentValue, limit = 30) {
252
+ const rawQuery = String(query || "").trim().toLowerCase();
253
+ const current = canonicalTimezone(currentValue);
254
+ if (rawQuery === "") {
255
+ return Array.from(
256
+ new Set([current, BROWSER_TIMEZONE, ...POPULAR_TIMEZONES].filter(Boolean)),
257
+ ).slice(0, 12);
258
+ }
259
+ const normalizedQuery = timezoneSearchKey(rawQuery);
260
+ if (normalizedQuery === "") return [];
261
+ const terms = normalizedQuery.split(" ").filter(Boolean);
262
+ return TIMEZONE_SEARCH_INDEX
263
+ .filter((entry) => terms.every((term) => entry.search.includes(term)))
264
+ .map((entry) => {
265
+ const canonical = entry.zone.toLowerCase();
266
+ const preferred = timezonePreferredValue(entry.zone).toLowerCase();
267
+ const city = timezoneSearchKey(entry.city);
268
+ let score = 5;
269
+ if (canonical === rawQuery || preferred === rawQuery) score = 0;
270
+ else if (city === normalizedQuery) score = 1;
271
+ else if (city.startsWith(normalizedQuery)) score = 2;
272
+ else if (canonical.split("/").some((segment) => timezoneSearchKey(segment).startsWith(normalizedQuery))) score = 3;
273
+ else if (entry.search.startsWith(normalizedQuery)) score = 4;
274
+ return { ...entry, score };
275
+ })
276
+ .sort((left, right) => left.score - right.score || left.city.localeCompare(right.city) || left.zone.localeCompare(right.zone))
277
+ .slice(0, limit)
278
+ .map((entry) => entry.zone);
279
+ }
280
+
281
+ function timezoneNavigationIndex(key, current, resultCount, reopening) {
282
+ if (resultCount === 0) return -1;
283
+ if (reopening) return key === "ArrowDown" ? 0 : resultCount - 1;
284
+ if (key === "ArrowDown") return current < 0 || current >= resultCount - 1 ? 0 : current + 1;
285
+ return current <= 0 ? resultCount - 1 : current - 1;
286
+ }
287
+
288
+ function timezoneOptionPresentation(zone, offsetCache) {
289
+ let offset = offsetCache?.get(zone);
290
+ if (!offset) {
291
+ offset = timezoneOffsetLabel(zone);
292
+ offsetCache?.set(zone, offset);
293
+ }
294
+ const current = BROWSER_TIMEZONE === zone;
295
+ const preferred = timezonePreferredValue(zone);
296
+ if (zone === "UTC") {
297
+ return {
298
+ label: "UTC",
299
+ detail: offset + " now · Coordinated Universal Time" + (current ? " · Your browser time zone" : ""),
300
+ };
301
+ }
302
+ return {
303
+ label: timezoneCityLabel(preferred),
304
+ detail: preferred + " · " + offset + " now" + (current ? " · Your browser time zone" : ""),
305
+ };
306
+ }
307
+
150
308
  function browserTimezone() {
151
309
  return BROWSER_TIMEZONE;
152
310
  }
@@ -446,6 +604,310 @@ window.__ModuleLoader__.load({
446
604
  );
447
605
  }
448
606
 
607
+ function useTimeZonePopoverLayout(open, anchorRef, panelRef) {
608
+ const [layout, setLayout] = useState(null);
609
+ useEffect(() => {
610
+ if (!open) {
611
+ setLayout(null);
612
+ return undefined;
613
+ }
614
+ const place = () => {
615
+ const anchor = anchorRef.current;
616
+ if (!anchor) return;
617
+ const rect = anchor.getBoundingClientRect();
618
+ const panel = panelRef.current;
619
+ const margin = 12;
620
+ const gap = 4;
621
+ const width = Math.min(Math.max(rect.width, 280), window.innerWidth - margin * 2);
622
+ const left = Math.min(Math.max(rect.left, margin), window.innerWidth - width - margin);
623
+ const desiredHeight = Math.min(360, panel?.scrollHeight || 360);
624
+ const below = window.innerHeight - rect.bottom - gap - margin;
625
+ const above = rect.top - gap - margin;
626
+ const side = below >= Math.min(desiredHeight, 220) || below >= above ? "bottom" : "top";
627
+ const available = Math.max(80, side === "bottom" ? below : above);
628
+ const maxHeight = Math.min(360, available);
629
+ const renderedHeight = Math.min(panel?.offsetHeight || desiredHeight, maxHeight);
630
+ const top = side === "bottom"
631
+ ? rect.bottom + gap
632
+ : Math.max(margin, rect.top - gap - renderedHeight);
633
+ setLayout((current) => {
634
+ const next = { left, top, width, maxHeight, side };
635
+ return current
636
+ && current.left === next.left
637
+ && current.top === next.top
638
+ && current.width === next.width
639
+ && current.maxHeight === next.maxHeight
640
+ && current.side === next.side
641
+ ? current
642
+ : next;
643
+ });
644
+ };
645
+ const frame = requestAnimationFrame(place);
646
+ window.addEventListener("scroll", place, true);
647
+ window.addEventListener("resize", place);
648
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(place);
649
+ if (anchorRef.current) observer?.observe(anchorRef.current);
650
+ if (panelRef.current) observer?.observe(panelRef.current);
651
+ return () => {
652
+ cancelAnimationFrame(frame);
653
+ observer?.disconnect();
654
+ window.removeEventListener("scroll", place, true);
655
+ window.removeEventListener("resize", place);
656
+ };
657
+ }, [open, anchorRef, panelRef]);
658
+ return layout;
659
+ }
660
+
661
+ function TimeZonePicker({ id, value, onChange }) {
662
+ const [open, setOpen] = useState(false);
663
+ const [searching, setSearching] = useState(false);
664
+ const [activeIndex, setActiveIndex] = useState(-1);
665
+ const [announcement, setAnnouncement] = useState("");
666
+ const anchorRef = useRef(null);
667
+ const inputRef = useRef(null);
668
+ const panelRef = useRef(null);
669
+ const offsetCacheRef = useRef(new Map());
670
+ const listboxId = id + "-options";
671
+ const query = searching ? value : "";
672
+ const results = timezoneSearchResults(query, value);
673
+ const active = activeIndex >= 0 && activeIndex < results.length ? activeIndex : -1;
674
+ const selected = canonicalTimezone(value);
675
+ const resumeSearch = value.trim() !== "" && selected === null;
676
+ const popoverLayout = useTimeZonePopoverLayout(open, anchorRef, panelRef);
677
+ const close = () => {
678
+ setOpen(false);
679
+ setSearching(false);
680
+ setActiveIndex(-1);
681
+ setAnnouncement("");
682
+ };
683
+ const show = () => {
684
+ offsetCacheRef.current.clear();
685
+ setAnnouncement("");
686
+ setOpen(true);
687
+ };
688
+ const choose = (zone) => {
689
+ onChange(timezonePreferredValue(zone));
690
+ close();
691
+ requestAnimationFrame(() => inputRef.current?.focus());
692
+ };
693
+
694
+ useEffect(() => {
695
+ if (!open) return undefined;
696
+ const dismiss = (event) => {
697
+ const target = event.target;
698
+ if (!(target instanceof Node)) return;
699
+ if (anchorRef.current?.contains(target) || panelRef.current?.contains(target)) return;
700
+ close();
701
+ };
702
+ document.addEventListener("pointerdown", dismiss, true);
703
+ return () => document.removeEventListener("pointerdown", dismiss, true);
704
+ }, [open]);
705
+
706
+ useEffect(() => {
707
+ if (!open) return undefined;
708
+ const timer = window.setTimeout(() => {
709
+ setAnnouncement(
710
+ results.length === 0
711
+ ? "No matching time zones."
712
+ : results.length + (results.length === 1 ? " time zone suggestion available." : " time zone suggestions available."),
713
+ );
714
+ }, 250);
715
+ return () => window.clearTimeout(timer);
716
+ }, [open, query, results.length]);
717
+
718
+ useEffect(() => {
719
+ if (!open || active < 0) return;
720
+ document.getElementById(listboxId + "-option-" + active)?.scrollIntoView({ block: "nearest" });
721
+ }, [open, active, listboxId]);
722
+
723
+ const onInputKeyDown = (event) => {
724
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
725
+ event.preventDefault();
726
+ const navigationResults = !open && resumeSearch
727
+ ? timezoneSearchResults(value, value)
728
+ : results;
729
+ if (!open) {
730
+ setSearching(resumeSearch);
731
+ show();
732
+ }
733
+ if (navigationResults.length === 0) {
734
+ setActiveIndex(-1);
735
+ return;
736
+ }
737
+ setActiveIndex((current) =>
738
+ timezoneNavigationIndex(event.key, current, navigationResults.length, !open),
739
+ );
740
+ return;
741
+ }
742
+ if (event.key === "Enter" && open) {
743
+ event.preventDefault();
744
+ if (active >= 0) {
745
+ choose(results[active]);
746
+ return;
747
+ }
748
+ const canonical = canonicalTimezone(value);
749
+ if (canonical) choose(canonical);
750
+ else setAnnouncement("Enter a valid IANA time zone, such as Europe/Berlin.");
751
+ return;
752
+ }
753
+ if (event.key === "Escape" && open) {
754
+ event.preventDefault();
755
+ event.stopPropagation();
756
+ (event.nativeEvent || event).__dshAutomationsNested = true;
757
+ close();
758
+ return;
759
+ }
760
+ if (event.key === "Tab" && open) close();
761
+ };
762
+
763
+ const panel = open
764
+ ? h(
765
+ "div",
766
+ {
767
+ ref: panelRef,
768
+ className: "dsh-auto-timezone-popover",
769
+ style: popoverLayout
770
+ ? {
771
+ left: popoverLayout.left,
772
+ top: popoverLayout.top,
773
+ width: popoverLayout.width,
774
+ maxHeight: popoverLayout.maxHeight,
775
+ }
776
+ : { left: 12, top: 12, width: 320, visibility: "hidden" },
777
+ "data-side": popoverLayout?.side,
778
+ "data-dsh-auto-timezone-popover": "true",
779
+ },
780
+ h(
781
+ "div",
782
+ { className: "dsh-auto-timezone-popover-title" },
783
+ searching && query.trim() !== "" ? "Matching time zones" : "Suggested time zones",
784
+ ),
785
+ h(
786
+ "div",
787
+ {
788
+ id: listboxId,
789
+ className: "dsh-auto-timezone-list",
790
+ role: "listbox",
791
+ "aria-label": "Time zones",
792
+ },
793
+ results.length > 0
794
+ ? results.map((zone, index) => {
795
+ const copy = timezoneOptionPresentation(zone, offsetCacheRef.current);
796
+ const optionId = listboxId + "-option-" + index;
797
+ return h(
798
+ "div",
799
+ {
800
+ key: zone,
801
+ id: optionId,
802
+ className: "dsh-auto-timezone-option",
803
+ role: "option",
804
+ "aria-selected": selected === zone,
805
+ "data-active": active === index ? "true" : undefined,
806
+ onPointerMove: () => setActiveIndex(index),
807
+ onPointerDown: (event) => event.preventDefault(),
808
+ onClick: () => choose(zone),
809
+ },
810
+ h("span", { className: "dsh-auto-timezone-option-icon", "aria-hidden": "true" }, h(IconGlobeOutline14)),
811
+ h(
812
+ "span",
813
+ { className: "dsh-auto-picker-item-copy" },
814
+ h("span", { className: "dsh-auto-picker-item-label" }, copy.label),
815
+ h("span", { className: "dsh-auto-picker-item-detail" }, copy.detail),
816
+ ),
817
+ selected === zone
818
+ ? h("span", { className: "dsh-auto-timezone-option-check", "aria-hidden": "true" }, h(IconCheckOutline16))
819
+ : null,
820
+ );
821
+ })
822
+ : h(
823
+ "div",
824
+ {
825
+ className: "dsh-auto-timezone-empty",
826
+ role: "option",
827
+ "aria-disabled": "true",
828
+ "aria-selected": false,
829
+ },
830
+ "No matching time zones. You can still enter a recognized IANA time zone, such as Europe/Berlin.",
831
+ ),
832
+ ),
833
+ h("p", { className: "dsh-auto-timezone-popover-hint" }, "Type a city, region, or IANA time zone."),
834
+ )
835
+ : null;
836
+
837
+ return h(
838
+ React.Fragment,
839
+ null,
840
+ h(
841
+ "span",
842
+ {
843
+ ref: anchorRef,
844
+ className: "dsh-auto-timezone-control",
845
+ "data-open": open ? "true" : undefined,
846
+ onPointerDown: (event) => {
847
+ if (event.target === inputRef.current) return;
848
+ event.preventDefault();
849
+ if (open) close();
850
+ else {
851
+ setSearching(resumeSearch);
852
+ show();
853
+ requestAnimationFrame(() => {
854
+ inputRef.current?.focus();
855
+ inputRef.current?.select();
856
+ });
857
+ }
858
+ },
859
+ },
860
+ h("span", { className: "dsh-auto-timezone-control-icon", "aria-hidden": "true" }, h(IconGlobeOutline14)),
861
+ h("input", {
862
+ ref: inputRef,
863
+ id,
864
+ className: "dsh-auto-timezone-input",
865
+ type: "text",
866
+ role: "combobox",
867
+ value,
868
+ placeholder: "Search city or time zone",
869
+ autoComplete: "off",
870
+ spellCheck: false,
871
+ "aria-autocomplete": "list",
872
+ "aria-haspopup": "listbox",
873
+ "aria-expanded": open,
874
+ "aria-controls": open ? listboxId : undefined,
875
+ "aria-activedescendant": open && active >= 0 ? listboxId + "-option-" + active : undefined,
876
+ "aria-invalid": !open && value.trim() !== "" && selected === null ? "true" : undefined,
877
+ onFocus: (event) => {
878
+ setSearching(resumeSearch);
879
+ setActiveIndex(-1);
880
+ show();
881
+ event.currentTarget.select();
882
+ },
883
+ onClick: () => {
884
+ if (open) return;
885
+ setSearching(resumeSearch);
886
+ setActiveIndex(-1);
887
+ show();
888
+ },
889
+ onChange: (event) => {
890
+ setSearching(true);
891
+ setActiveIndex(0);
892
+ show();
893
+ onChange(event.target.value);
894
+ },
895
+ onKeyDown: onInputKeyDown,
896
+ }),
897
+ h(
898
+ "span",
899
+ {
900
+ className: "dsh-auto-timezone-chevron" + (open ? " dsh-auto-timezone-chevron-open" : ""),
901
+ "aria-hidden": "true",
902
+ },
903
+ h(IconChevronDownOutline14),
904
+ ),
905
+ ),
906
+ open && typeof document !== "undefined" ? createPortal(panel, document.body) : null,
907
+ h("span", { className: "dsh-auto-sr-only", role: "status", "aria-live": "polite" }, announcement),
908
+ );
909
+ }
910
+
449
911
  function AgentPresetPicker({ id, value, presets, onChange }) {
450
912
  const [open, setOpen] = useState(false);
451
913
  const ownerId = useId();
@@ -692,21 +1154,17 @@ window.__ModuleLoader__.load({
692
1154
  ),
693
1155
  h(
694
1156
  Field,
695
- { label: "Timezone", htmlFor: fieldId("timezone"), required: true },
696
- h("input", {
1157
+ {
1158
+ label: "Time zone",
1159
+ htmlFor: fieldId("timezone"),
1160
+ required: true,
1161
+ hint: "Enter an IANA time zone. Current UTC offsets may change with daylight saving time.",
1162
+ },
1163
+ h(TimeZonePicker, {
697
1164
  id: fieldId("timezone"),
698
- className: "dsh-auto-input",
699
- type: "text",
700
- list: fieldId("timezone-list"),
701
1165
  value: draft.timezone,
702
- spellCheck: false,
703
- onChange: (event) => onChange("timezone", event.target.value),
1166
+ onChange: (value) => onChange("timezone", value),
704
1167
  }),
705
- h(
706
- "datalist",
707
- { id: fieldId("timezone-list") },
708
- TIMEZONES.map((zone) => h("option", { key: zone, value: zone })),
709
- ),
710
1168
  ),
711
1169
  h(
712
1170
  Field,
@@ -1657,7 +2115,7 @@ window.__ModuleLoader__.load({
1657
2115
  const onKeyDown = (event) => {
1658
2116
  if (event.key !== "Escape" || event.defaultPrevented || event.__dshAutomationsNested) return;
1659
2117
  if (event.target?.closest?.('[role="dialog"],[role="menu"]')) return;
1660
- if (document.querySelector('.dsh-auto-picker-trigger[aria-expanded="true"]')) return;
2118
+ if (document.querySelector('.dsh-auto-picker-trigger[aria-expanded="true"],.dsh-auto-timezone-input[aria-expanded="true"]')) return;
1661
2119
  event.preventDefault();
1662
2120
  disclosure.close();
1663
2121
  };
@@ -1745,7 +2203,29 @@ window.__ModuleLoader__.load({
1745
2203
  ".dsh-auto-picker-chevron-open{transform:rotate(180deg);}",
1746
2204
  ".dsh-auto-picker-item-copy{min-width:0;display:flex;flex-direction:column;white-space:normal;}",
1747
2205
  ".dsh-auto-picker-item-label{color:inherit;font-size:14px;line-height:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}",
1748
- ".dsh-auto-picker-item-detail{color:var(--dsh-auto-muted);font-size:12px;line-height:18px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}",
2206
+ ".dsh-auto-picker-item-detail{color:var(--dsh-auto-muted,var(--dsw-alias-label-tertiary,#81858c));font-size:12px;line-height:18px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}",
2207
+ ".dsh-auto-timezone-control{width:100%;height:34px;padding:0 8px;display:flex;align-items:center;gap:7px;border:1px solid var(--dsh-auto-border);border-radius:8px;background:var(--dsh-auto-input-bg);color:var(--dsh-auto-text);cursor:text;}",
2208
+ ".dsh-auto-timezone-control:hover{background:var(--dsw-alias-interactive-bg-hover,var(--dsh-auto-input-bg));}",
2209
+ ".dsh-auto-timezone-control:focus-within,.dsh-auto-timezone-control[data-open=\"true\"]{border-color:var(--dsh-auto-accent);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsh-auto-accent) 18%,transparent);}",
2210
+ ".dsh-auto-timezone-control:has(.dsh-auto-timezone-input[aria-invalid=\"true\"]){border-color:var(--dsh-auto-danger);}",
2211
+ ".dsh-auto-timezone-control-icon,.dsh-auto-timezone-chevron{width:16px;height:16px;flex:none;display:inline-flex;align-items:center;justify-content:center;color:var(--dsh-auto-muted);pointer-events:none;}",
2212
+ ".dsh-auto-timezone-input{appearance:none;min-width:0;height:100%;flex:1;padding:0;border:0;outline:0;background:transparent;color:inherit;font:inherit;font-size:13px;line-height:20px;}",
2213
+ ".dsh-auto-timezone-input::placeholder{color:var(--dsh-auto-caption);}",
2214
+ ".dsh-auto-timezone-chevron{transition:transform .12s ease;}",
2215
+ ".dsh-auto-timezone-chevron-open{transform:rotate(180deg);}",
2216
+ ".dsh-auto-timezone-popover{box-sizing:border-box;position:fixed;z-index:1100;max-height:min(360px,calc(100vh - 24px));padding:4px;display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-inverted,var(--dsw-alias-border-l2,rgba(15,17,21,.16)));border-radius:12px;background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-2,#fff));box-shadow:var(--dsw-shadow-lv3,0 12px 32px rgba(15,17,21,.16));color:var(--dsw-alias-label-primary,#0f1115);font-family:inherit;}",
2217
+ ".dsh-auto-timezone-popover *,.dsh-auto-timezone-popover *::before,.dsh-auto-timezone-popover *::after{box-sizing:border-box;}",
2218
+ ".dsh-auto-timezone-popover-title{flex:none;padding:7px 10px 5px;color:var(--dsw-alias-label-tertiary,#81858c);font-size:11px;font-weight:600;line-height:16px;text-transform:uppercase;letter-spacing:.04em;}",
2219
+ ".dsh-auto-timezone-list{min-height:0;overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain;display:flex;flex-direction:column;--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);}",
2220
+ ".dsh-auto-timezone-option{width:100%;min-height:48px;padding:6px 9px;display:flex;align-items:center;gap:8px;border-radius:9px;cursor:pointer;user-select:none;}",
2221
+ ".dsh-auto-timezone-option:hover,.dsh-auto-timezone-option[data-active=\"true\"]{background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06));}",
2222
+ ".dsh-auto-timezone-option[aria-selected=\"true\"]{color:var(--dsw-alias-state-business-primary,#4176e6);}",
2223
+ ".dsh-auto-timezone-option-icon,.dsh-auto-timezone-option-check{width:16px;height:16px;flex:none;display:inline-flex;align-items:center;justify-content:center;color:var(--dsw-alias-label-tertiary,#81858c);}",
2224
+ ".dsh-auto-timezone-option>.dsh-auto-picker-item-copy{min-width:0;flex:1;}",
2225
+ ".dsh-auto-timezone-option .dsh-auto-picker-item-detail{color:var(--dsw-alias-label-tertiary,#81858c);}",
2226
+ ".dsh-auto-timezone-option-check{color:var(--dsw-alias-state-business-primary,#4176e6);}",
2227
+ ".dsh-auto-timezone-empty{margin:0;padding:16px 12px;color:var(--dsw-alias-label-secondary,#4f5661);font-size:12px;line-height:18px;text-align:center;}",
2228
+ ".dsh-auto-timezone-popover-hint{flex:none;margin:4px 0 0;padding:7px 10px 5px;border-top:1px solid var(--dsw-alias-border-l2,rgba(15,17,21,.12));color:var(--dsw-alias-label-tertiary,#81858c);font-size:11px;line-height:16px;}",
1749
2229
  ".dsh-auto-permission-glyph{flex:none;}",
1750
2230
  ".dsh-auto-permission-glyph-read{color:var(--dsh-auto-muted);}",
1751
2231
  ".dsh-auto-permission-glyph-write{color:var(--dsh-auto-accent);}",
@@ -1888,7 +2368,16 @@ window.__ModuleLoader__.load({
1888
2368
  exports.agentPresetDisplayLabel = agentPresetDisplayLabel;
1889
2369
  exports.permissionPresetPresentation = permissionPresetPresentation;
1890
2370
  exports.PermissionPresetPicker = PermissionPresetPicker;
1891
- exports.__testing = Object.freeze({ nextFormInstancePrefix });
2371
+ exports.TimeZonePicker = TimeZonePicker;
2372
+ exports.__testing = Object.freeze({
2373
+ nextFormInstancePrefix,
2374
+ canonicalTimezone,
2375
+ timezoneOffsetLabel,
2376
+ timezoneNavigationIndex,
2377
+ timezoneOptionPresentation,
2378
+ timezonePreferredValue,
2379
+ timezoneSearchResults,
2380
+ });
1892
2381
  exports.inject = inject;
1893
2382
  exports.apply = apply;
1894
2383
  return module.exports;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncended/dsh-automations",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Durable cron automations for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -91,7 +91,8 @@
91
91
  "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
92
92
  "@deepseek-ai/dsh-permission-presets": "^0.1.1-rc.2",
93
93
  "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
94
- "react": "^18.2.0"
94
+ "react": "^18.2.0",
95
+ "react-dom": "^18.2.0"
95
96
  },
96
97
  "devDependencies": {
97
98
  "@deepseek-ai/cordis": "4.0.1",
@@ -110,6 +111,7 @@
110
111
  "@deepseek-ai/dsh-session": "0.1.1-rc.2",
111
112
  "@types/node": "^22.0.0",
112
113
  "react": "^18.2.0",
114
+ "react-dom": "^18.2.0",
113
115
  "typescript": "^5.9.3"
114
116
  }
115
117
  }