issue-sticker 0.11.0 → 0.13.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.
@@ -15,35 +15,175 @@ var __privateWrapper = (obj, member, setter, getter) => ({
15
15
  }
16
16
  });
17
17
  var _focused, _cleanup, _setup, _b, _provider, _providerCalled, _c, _online, _cleanup2, _setup2, _d, _gcTimeout, _e, _queryType, _initialState, _revertState, _cache, _client, _retryer, _defaultOptions, _abortSignalConsumed, _Query_instances, isInitialPausedFetch_fn, dispatch_fn, _f, _client2, _currentQuery, _currentQueryInitialState, _currentResult, _currentResultState, _currentResultOptions, _currentThenable, _selectError, _selectFn, _selectResult, _lastQueryWithDefinedData, _staleTimeoutId, _refetchIntervalId, _currentRefetchInterval, _trackedProps, _QueryObserver_instances, executeFetch_fn, updateStaleTimeout_fn, computeRefetchInterval_fn, updateRefetchInterval_fn, updateTimers_fn, clearStaleTimeout_fn, clearRefetchInterval_fn, updateQuery_fn, notify_fn, _g, _client3, _observers, _mutationCache, _retryer2, _Mutation_instances, dispatch_fn2, _h, _mutations, _scopes, _mutationId, _i, _client4, _currentResult2, _currentMutation, _mutateOptions, _MutationObserver_instances, updateResult_fn, notify_fn2, _j, _queries, _k, _queryCache, _mutationCache2, _defaultOptions2, _queryDefaults, _mutationDefaults, _mountCount, _unsubscribeFocus, _unsubscribeOnline, _l, _m;
18
- import { O as OVERLAY_LAYER_INDEX, c as createLucideIcon$1, d as usePortalContainer, a as cn$1, e as OVERLAY_LAYER, s as stopEscapePropagation, P as Presence, f as Primitive, g as composeEventHandlers, h as useDismissableLayerSurface, i as useComposedRefs$1, j as Portal$1, k as createContextScope, l as createSlot, D as DismissableLayer, m as useControllableState, n as useId$1, o as useLayoutEffect2, p as useCallbackRef, u as ungzip_1, q as PortalContainerProvider, r as registerThree } from "./three-DQdLNs7v.js";
19
- import { A as trimLogBackupPayload, B as createSafeLogBackup, D as getLogBackupSessionId, N as NetworkCollector, C as ConsoleCollector, W as WebSocketCollector, E as createIndexedDbLogBackup, b as buildJiraCreateFields, P as Popover, F as PopoverTrigger, G as Input, H as PopoverContent, L as Label, J as Select, K as SelectTrigger, M as SelectValue, O as SelectContent, Q as SelectItem, R as JIRA_ISSUE_TYPE_VALUE_KEY, T as getJiraMetadata, U as buildJiraIssueFields, V as getJiraIssueTypeLabel, X as Button, Y as Root2$2, Z as Trigger$2, _ as ChevronDown, $ as Portal, a0 as Content2$1, a1 as Check, a2 as X$1, a3 as DatePicker, e as buildNotionCreateFields, a4 as getNotionPageMetadata, a5 as buildNotionFieldViews, c as captureWithModernScreenshot, p as pristineFetch, d as directUploadToSignedUrl, a6 as DirectUploadError, a7 as toast, a8 as getSnapshot, a9 as subscribe$1, a as uploadSignedMediaWithApi, t as sanitizeString, aa as getRegisteredUploadId, f as buildGuestOwnerQuery, j as devLogger, x as getCurrentPageUrl, ab as ChevronRight, ac as ReactRemoveScroll, ad as hideOthers, ae as useFocusGuards, af as FocusScope, ag as getExposedJiraFields, ah as isJiraFieldExposed, ai as normalizeExposedJiraFields, z as preloadModernScreenshot, g as getElementPosition, u as uploadScreenshotDataUrls, k as filterExcludeServiceRelatedLogs, l as filterExcludeServiceRelatedConsoleLogs, s as selectNetworkLogsForSubmit, m as compactNetworkLogsForTransport, n as CONSOLE_LOG_SUBMIT_LIMIT, o as buildExternalUserPayload, v as serializeSocketTranscript, w as uploadSocketTranscript, q as shrinkNetworkLogsToFit, r as measureJsonBytes, i as buildIssueIntegrationsPayload, y as SessionReplayCaptureController, aj as resolveJiraIssueTypeId, ak as pickJiraIssueTypeFields, al as switchJiraIssueType, am as getPrimaryJiraMapping, an as createCollection, ao as useDirection, ap as getPrimaryNotionMapping, aq as buttonVariants, ar as TEST_ATTR_KEYS, as as jaccard, at as textSimilarity, au as collectHeadings, av as buildElementLocator, aw as normalizeForCompare, ax as CONTAINER_SELECTOR, ay as safeRole, az as safeAccessibleName, aA as sameDigitGroups, aB as labelSimilarityKeepingDigits, aC as landmarkRole, aD as findElementBySelector, aE as findElementByXPath, S as ScreenRecordingController, h as composeIssueFilterGroups, aF as OnboardingProvider, aG as useOnboardingTarget, aH as useOnboardingFlow, aI as PopoverAnchor } from "./externalUserPayload-BbOEmKZD.js";
18
+ import { M as MASKING_COPY, O as OVERLAY_LAYER_INDEX, c as createLucideIcon$1, h as usePortalContainer, e as cn$1, i as OVERLAY_LAYER, s as stopEscapePropagation, j as useControllableState, k as useId$1, P as Presence, l as Primitive, m as composeEventHandlers, n as useDismissableLayerSurface, o as useComposedRefs$1, p as Portal$1, q as createContextScope, t as createSlot, D as DismissableLayer, v as useLayoutEffect2, w as useCallbackRef, u as ungzip_1, x as PortalContainerProvider, r as registerThree } from "./three-FamoZZKK.js";
19
+ import { a8 as toast, a9 as MASKING_NOTICE_TOAST_DURATION_MS, aa as trimLogBackupPayload, ab as createSafeLogBackup, ac as getLogBackupSessionId, N as NetworkCollector, C as ConsoleCollector, W as WebSocketCollector, ad as createIndexedDbLogBackup, e as buildJiraCreateFields, ae as Popover, af as PopoverTrigger, ag as Input, ah as PopoverContent, ai as Label, aj as Select, ak as SelectTrigger, al as SelectValue, am as SelectContent, an as SelectItem, ao as JIRA_ISSUE_TYPE_VALUE_KEY, ap as getJiraMetadata, aq as buildJiraIssueFields, ar as getJiraIssueTypeLabel, as as Button, at as Root2$2, au as Trigger$2, av as ChevronDown, aw as Portal, ax as Content2$1, ay as Check, az as X$1, aA as DatePicker, f as buildNotionCreateFields, aB as getNotionPageMetadata, aC as buildNotionFieldViews, b as captureScreenshotWithMasks, p as pristineFetch, d as directUploadToSignedUrl, aD as DirectUploadError, a as uploadSignedMediaWithApi, w as sanitizeString, aE as getRegisteredUploadId, h as buildGuestOwnerQuery, Q as createIssueUploadQueues, a2 as loadTusClient, a3 as createIndexedDbPendingUploadStore, a6 as createMediaUploadApi, a4 as ATTACHMENT_UPLOAD_DB_NAME, a5 as VIDEO_UPLOAD_DB_NAME, a1 as createMaskingPolicySource, a7 as createMemoryMaskingPolicyCache, Y as MASKING_POLICY_PREFETCH_TIMEOUT_MS, aF as getSnapshot, aG as subscribe$1, l as devLogger, P as getCurrentPageUrl, aH as ChevronRight, aI as ReactRemoveScroll, aJ as hideOthers, aK as useFocusGuards, aL as FocusScope, aM as createShadowDismissRoot, aN as getExposedJiraFields, aO as isJiraFieldExposed, aP as normalizeExposedJiraFields, u as uploadScreenshotDataUrls, c as captureViewportAndElementShots, M as MaskingCaptureError, T as preloadModernScreenshot, g as getElementPosition, _ as loadCaptureSelectors, i as isCapturedScreenshot, F as collectSnapshotSelectors, G as MaskingScreenRulesChangedError, K as assertScreenRulesCovered, L as isMaskingFailure, O as maskScreenshotsForSubmit, aQ as NO_MASKING_OUTCOME, aR as describeMaskingSubmitFailure, m as filterExcludeServiceRelatedLogs, n as filterExcludeServiceRelatedConsoleLogs, s as selectNetworkLogsForSubmit, o as compactNetworkLogsForTransport, q as CONSOLE_LOG_SUBMIT_LIMIT, k as buildIssueIntegrationsPayload, x as runMaskedSubmit, y as applySubmitMasking, r as buildExternalUserPayload, A as maskPositionForSubmit, D as appendMaskingNotice, E as screenshotExclusionNotice, z as collectAttachmentUploads, B as hasReplayUpload, t as shrinkNetworkLogsToFit, v as measureJsonBytes, U as enqueueIssueUploads, V as normalizeUploadMimeType, R as SessionReplayCaptureController, aS as resolveJiraIssueTypeId, aT as pickJiraIssueTypeFields, aU as switchJiraIssueType, a0 as resolveEffectivePolicy, $ as loadMaskingPolicyForSubmit, aV as getPrimaryNotionMapping, aW as getPrimaryJiraMapping, aX as createCollection, aY as useDirection, aZ as useShadowOutsideDismiss, a_ as buttonVariants, a$ as TEST_ATTR_KEYS, b0 as jaccard, b1 as textSimilarity, b2 as collectHeadings, b3 as buildElementLocator, b4 as normalizeForCompare, b5 as CONTAINER_SELECTOR, b6 as safeRole, b7 as safeAccessibleName, b8 as sameDigitGroups, b9 as labelSimilarityKeepingDigits, ba as landmarkRole, bb as findElementBySelector, bc as findElementByXPath, S as ScreenRecordingController, bd as maskingCompletionNotice, j as composeIssueFilterGroups, be as OnboardingProvider, bf as useOnboardingTarget, bg as useOnboardingFlow, bh as PopoverAnchor } from "./externalUserPayload-CdzgCq4t.js";
20
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
20
21
  import * as React from "react";
21
22
  import { useRef, useState, useEffect, useId, useLayoutEffect, useCallback, forwardRef, createElement, createContext, useContext, Component, Suspense, useInsertionEffect, useMemo, Children, isValidElement, Fragment as Fragment$1, useSyncExternalStore, useDeferredValue } from "react";
22
- import { jsxs, jsx, Fragment } from "react/jsx-runtime";
23
23
  import { createRoot } from "react-dom/client";
24
24
  import { createPortal } from "react-dom";
25
- import { T as TooltipProvider, a as Tooltip, b as TooltipTrigger, c as TooltipContent } from "./tooltip-DSCAsWIp.js";
26
- function readReleaseMarker() {
27
- try {
28
- const g = globalThis.__ISSUE_STICKER_RELEASE__;
29
- if (g && typeof g.id === "string" && g.id.length > 0) {
30
- return {
31
- release: g.id,
32
- release_source: typeof g.source === "string" && g.source.length > 0 ? g.source : "global"
33
- };
25
+ import { T as TooltipProvider, a as Tooltip, b as TooltipTrigger, c as TooltipContent } from "./tooltip-BVA6ikuJ.js";
26
+ import { g as getDefaultExportFromCjs } from "./_commonjsHelpers-DWwsNxpa.js";
27
+ function debounce(func, debounceMs, { signal, edges } = {}) {
28
+ let pendingThis = void 0;
29
+ let pendingArgs = null;
30
+ const leading = edges != null && edges.includes("leading");
31
+ const trailing = edges == null || edges.includes("trailing");
32
+ const invoke = () => {
33
+ if (pendingArgs !== null) {
34
+ func.apply(pendingThis, pendingArgs);
35
+ pendingThis = void 0;
36
+ pendingArgs = null;
34
37
  }
35
- if (typeof document !== "undefined") {
36
- const meta = document.querySelector('meta[name="issue-sticker:release"]');
37
- const content2 = meta == null ? void 0 : meta.getAttribute("content");
38
- if (content2 && content2.length > 0) {
39
- return { release: content2, release_source: "meta" };
40
- }
38
+ };
39
+ const onTimerEnd = () => {
40
+ if (trailing) invoke();
41
+ cancel();
42
+ };
43
+ let timeoutId = null;
44
+ const schedule = () => {
45
+ if (timeoutId != null) clearTimeout(timeoutId);
46
+ timeoutId = setTimeout(() => {
47
+ timeoutId = null;
48
+ onTimerEnd();
49
+ }, debounceMs);
50
+ };
51
+ const cancelTimer = () => {
52
+ if (timeoutId !== null) {
53
+ clearTimeout(timeoutId);
54
+ timeoutId = null;
41
55
  }
42
- } catch {
43
- }
44
- return null;
56
+ };
57
+ const cancel = () => {
58
+ cancelTimer();
59
+ pendingThis = void 0;
60
+ pendingArgs = null;
61
+ };
62
+ const flush = () => {
63
+ invoke();
64
+ };
65
+ const debounced = function(...args) {
66
+ if (signal == null ? void 0 : signal.aborted) return;
67
+ pendingThis = this;
68
+ pendingArgs = args;
69
+ const isFirstCall = timeoutId == null;
70
+ schedule();
71
+ if (leading && isFirstCall) invoke();
72
+ };
73
+ debounced.schedule = schedule;
74
+ debounced.cancel = cancel;
75
+ debounced.flush = flush;
76
+ signal == null ? void 0 : signal.addEventListener("abort", cancel, { once: true });
77
+ return debounced;
78
+ }
79
+ function throttle(func, throttleMs, { signal, edges = ["leading", "trailing"] } = {}) {
80
+ let pendingAt = null;
81
+ const debounced = debounce(function(...args) {
82
+ pendingAt = Date.now();
83
+ func.apply(this, args);
84
+ }, throttleMs, {
85
+ signal,
86
+ edges
87
+ });
88
+ const throttled = function(...args) {
89
+ if (pendingAt == null) pendingAt = Date.now();
90
+ if (Date.now() - pendingAt >= throttleMs) {
91
+ pendingAt = Date.now();
92
+ func.apply(this, args);
93
+ debounced.cancel();
94
+ debounced.schedule();
95
+ return;
96
+ }
97
+ debounced.apply(this, args);
98
+ };
99
+ throttled.cancel = debounced.cancel;
100
+ throttled.flush = debounced.flush;
101
+ return throttled;
45
102
  }
46
103
  const ISSUE_STICKER_REPORT_FORM_CLASS = "issue-sticker-report-form";
104
+ function summarizeVideoUploads(states) {
105
+ if (states.length === 0) return void 0;
106
+ const remaining = states.filter((state) => state.status !== "failed");
107
+ const current2 = states.find((state) => state.status === "uploading");
108
+ const percent2 = current2 && current2.bytesTotal > 0 ? Math.floor(current2.bytesSent / current2.bytesTotal * 100) : 0;
109
+ return { percent: percent2, count: remaining.length, failed: remaining.length === 0 };
110
+ }
111
+ const VIDEO_UPLOAD_COPY = {
112
+ created: "이슈가 생성되었습니다. 영상 업로드가 끝나면 이슈에 첨부됩니다.",
113
+ resumed: (count) => `업로드하지 못한 영상 ${count}개를 이어서 업로드합니다.`
114
+ };
115
+ function visibleKinds(kinds) {
116
+ const video = kinds.includes("video");
117
+ const replay = kinds.includes("replay");
118
+ if (video && replay) return "both";
119
+ if (video) return "video";
120
+ return replay ? "replay" : null;
121
+ }
122
+ const ISSUE_UPLOAD_COPY = {
123
+ failedMessage: "네트워크 연결을 확인한 뒤 다시 시도해주세요.",
124
+ retry: "다시 시도",
125
+ failedTitle: {
126
+ video: "영상 업로드 실패",
127
+ replay: "세션 리플레이 업로드 실패",
128
+ both: "영상·세션 리플레이 업로드 실패"
129
+ },
130
+ aborted: {
131
+ video: "영상을 업로드하지 못했습니다.",
132
+ replay: "세션 리플레이를 업로드하지 못했습니다.",
133
+ both: "영상과 세션 리플레이를 업로드하지 못했습니다."
134
+ },
135
+ /** 완료는 영상이 있을 때만 알린다. 리플레이만 붙은 경우의 문구는 없다. */
136
+ attached: {
137
+ video: "영상이 이슈에 첨부되었습니다.",
138
+ both: "영상과 세션 리플레이가 이슈에 첨부되었습니다."
139
+ }
140
+ };
141
+ function createIssueUploadToaster() {
142
+ const failureToastIds = /* @__PURE__ */ new Map();
143
+ return {
144
+ show(notice, retry) {
145
+ var _a;
146
+ const previous2 = failureToastIds.get(notice.issueId);
147
+ if (previous2 !== void 0) {
148
+ toast.dismiss(previous2);
149
+ failureToastIds.delete(notice.issueId);
150
+ }
151
+ const failed = visibleKinds(notice.failed);
152
+ if (failed) {
153
+ const id2 = toast.error(ISSUE_UPLOAD_COPY.failedMessage, {
154
+ title: ISSUE_UPLOAD_COPY.failedTitle[failed],
155
+ duration: 0,
156
+ action: {
157
+ label: ISSUE_UPLOAD_COPY.retry,
158
+ onClick: () => {
159
+ failureToastIds.delete(notice.issueId);
160
+ retry(notice.issueId);
161
+ }
162
+ }
163
+ });
164
+ failureToastIds.set(notice.issueId, id2);
165
+ return;
166
+ }
167
+ let aborted = visibleKinds(notice.aborted);
168
+ if (((_a = notice.abortReasons) == null ? void 0 : _a.video) === "masking_video_excluded") {
169
+ toast.warning(MASKING_COPY.videoExcludedAfterCreate, {
170
+ duration: MASKING_NOTICE_TOAST_DURATION_MS
171
+ });
172
+ aborted = aborted === "both" ? "replay" : null;
173
+ }
174
+ if (aborted) {
175
+ toast.error(ISSUE_UPLOAD_COPY.aborted[aborted], {
176
+ title: ISSUE_UPLOAD_COPY.failedTitle[aborted]
177
+ });
178
+ return;
179
+ }
180
+ const completed = visibleKinds(notice.completed);
181
+ if (completed === "video" || completed === "both") {
182
+ toast.success(ISSUE_UPLOAD_COPY.attached[completed]);
183
+ }
184
+ }
185
+ };
186
+ }
47
187
  const DEFAULT_DEBOUNCE_MS = 1e3;
48
188
  function bindLogBackup(options) {
49
189
  const { store, sessionId, snapshot, restoreInto } = options;
@@ -235,82 +375,6 @@ function getIntegrationFailureMessage(service) {
235
375
  const action = FAILED_ACTION_TEXT[service] ?? "연동 작업을 마치지 못했습니다.";
236
376
  return `이슈는 저장했지만 ${action}`;
237
377
  }
238
- function debounce(func, debounceMs, { signal, edges } = {}) {
239
- let pendingThis = void 0;
240
- let pendingArgs = null;
241
- const leading = edges != null && edges.includes("leading");
242
- const trailing = edges == null || edges.includes("trailing");
243
- const invoke = () => {
244
- if (pendingArgs !== null) {
245
- func.apply(pendingThis, pendingArgs);
246
- pendingThis = void 0;
247
- pendingArgs = null;
248
- }
249
- };
250
- const onTimerEnd = () => {
251
- if (trailing) invoke();
252
- cancel();
253
- };
254
- let timeoutId = null;
255
- const schedule = () => {
256
- if (timeoutId != null) clearTimeout(timeoutId);
257
- timeoutId = setTimeout(() => {
258
- timeoutId = null;
259
- onTimerEnd();
260
- }, debounceMs);
261
- };
262
- const cancelTimer = () => {
263
- if (timeoutId !== null) {
264
- clearTimeout(timeoutId);
265
- timeoutId = null;
266
- }
267
- };
268
- const cancel = () => {
269
- cancelTimer();
270
- pendingThis = void 0;
271
- pendingArgs = null;
272
- };
273
- const flush = () => {
274
- invoke();
275
- };
276
- const debounced = function(...args) {
277
- if (signal == null ? void 0 : signal.aborted) return;
278
- pendingThis = this;
279
- pendingArgs = args;
280
- const isFirstCall = timeoutId == null;
281
- schedule();
282
- if (leading && isFirstCall) invoke();
283
- };
284
- debounced.schedule = schedule;
285
- debounced.cancel = cancel;
286
- debounced.flush = flush;
287
- signal == null ? void 0 : signal.addEventListener("abort", cancel, { once: true });
288
- return debounced;
289
- }
290
- function throttle(func, throttleMs, { signal, edges = ["leading", "trailing"] } = {}) {
291
- let pendingAt = null;
292
- const debounced = debounce(function(...args) {
293
- pendingAt = Date.now();
294
- func.apply(this, args);
295
- }, throttleMs, {
296
- signal,
297
- edges
298
- });
299
- const throttled = function(...args) {
300
- if (pendingAt == null) pendingAt = Date.now();
301
- if (Date.now() - pendingAt >= throttleMs) {
302
- pendingAt = Date.now();
303
- func.apply(this, args);
304
- debounced.cancel();
305
- debounced.schedule();
306
- return;
307
- }
308
- debounced.apply(this, args);
309
- };
310
- throttled.cancel = debounced.cancel;
311
- throttled.flush = debounced.flush;
312
- return throttled;
313
- }
314
378
  function getJiraFieldDefaultValues(exposedFields, fieldDefinitions = []) {
315
379
  const values = {};
316
380
  for (const field of buildJiraCreateFields(exposedFields, fieldDefinitions)) {
@@ -523,17 +587,105 @@ const HOST_POINTER_GUARD_EVENTS = [
523
587
  "touchstart",
524
588
  "touchend"
525
589
  ];
590
+ const HOST_CAPTURE_GUARD_EVENTS = [
591
+ "pointerdown",
592
+ "mousedown",
593
+ "click",
594
+ "dblclick",
595
+ "auxclick",
596
+ "contextmenu"
597
+ ];
598
+ const CLICK_EVENTS = /* @__PURE__ */ new Set(["click", "dblclick", "auxclick", "contextmenu"]);
599
+ const ACTIVATION_INPUT_TYPES = /* @__PURE__ */ new Set([
600
+ "checkbox",
601
+ "radio",
602
+ "submit",
603
+ "reset",
604
+ "image",
605
+ "file",
606
+ "color",
607
+ "date",
608
+ "datetime-local",
609
+ "month",
610
+ "time",
611
+ "week"
612
+ ]);
613
+ function hasActivationBehavior(node2) {
614
+ if (!(node2 instanceof Element)) return false;
615
+ if (node2.matches("a[href], area[href], label, summary, [popovertarget], [commandfor]")) {
616
+ return true;
617
+ }
618
+ if (node2 instanceof HTMLButtonElement) return node2.type !== "button" && node2.form !== null;
619
+ if (node2 instanceof HTMLInputElement) return ACTIVATION_INPUT_TYPES.has(node2.type);
620
+ return false;
621
+ }
622
+ function cloneInsideShadow(event) {
623
+ const init = {
624
+ bubbles: event.bubbles,
625
+ cancelable: event.cancelable,
626
+ composed: false,
627
+ view: event.view,
628
+ detail: event.detail,
629
+ screenX: event.screenX,
630
+ screenY: event.screenY,
631
+ clientX: event.clientX,
632
+ clientY: event.clientY,
633
+ movementX: event.movementX,
634
+ movementY: event.movementY,
635
+ button: event.button,
636
+ buttons: event.buttons,
637
+ relatedTarget: event.relatedTarget,
638
+ ctrlKey: event.ctrlKey,
639
+ shiftKey: event.shiftKey,
640
+ altKey: event.altKey,
641
+ metaKey: event.metaKey
642
+ };
643
+ if (typeof PointerEvent !== "undefined" && event instanceof PointerEvent) {
644
+ return new PointerEvent(event.type, {
645
+ ...init,
646
+ pointerId: event.pointerId,
647
+ pointerType: event.pointerType,
648
+ isPrimary: event.isPrimary,
649
+ width: event.width,
650
+ height: event.height,
651
+ pressure: event.pressure,
652
+ tangentialPressure: event.tangentialPressure,
653
+ tiltX: event.tiltX,
654
+ tiltY: event.tiltY,
655
+ twist: event.twist
656
+ });
657
+ }
658
+ return new MouseEvent(event.type, init);
659
+ }
526
660
  function attachHostPointerGuard(shadowRoot) {
661
+ const view = shadowRoot.ownerDocument.defaultView;
527
662
  const stopLeakingToPage = (event) => {
528
663
  event.stopPropagation();
529
664
  };
665
+ const forwardInsideWidget = (event) => {
666
+ const path2 = event.composedPath();
667
+ const origin = path2[0];
668
+ if (!(origin instanceof Node) || origin.getRootNode() !== shadowRoot) return;
669
+ if (CLICK_EVENTS.has(event.type)) {
670
+ const insideWidget = path2.slice(0, path2.indexOf(shadowRoot));
671
+ if (insideWidget.some(hasActivationBehavior)) return;
672
+ }
673
+ event.stopImmediatePropagation();
674
+ if (!origin.dispatchEvent(cloneInsideShadow(event))) event.preventDefault();
675
+ };
530
676
  HOST_POINTER_GUARD_EVENTS.forEach((type) => {
531
677
  shadowRoot.addEventListener(type, stopLeakingToPage);
532
678
  });
679
+ HOST_CAPTURE_GUARD_EVENTS.forEach((type) => {
680
+ view == null ? void 0 : view.addEventListener(type, forwardInsideWidget, true);
681
+ });
533
682
  return () => {
534
683
  HOST_POINTER_GUARD_EVENTS.forEach((type) => {
535
684
  shadowRoot.removeEventListener(type, stopLeakingToPage);
536
685
  });
686
+ HOST_CAPTURE_GUARD_EVENTS.forEach((type) => {
687
+ view == null ? void 0 : view.removeEventListener(type, forwardInsideWidget, true);
688
+ });
537
689
  };
538
690
  }
539
691
  const HOST_INTERACTION_GUARD_EVENTS = [
@@ -551,16 +703,32 @@ const HOST_INTERACTION_GUARD_EVENTS = [
551
703
  "dragend"
552
704
  ];
553
705
  function attachHostInteractionGuard(shadowRoot) {
706
+ const host = shadowRoot.host;
707
+ const view = shadowRoot.ownerDocument.defaultView;
554
708
  const stopLeakingToPage = (event) => {
555
709
  event.stopPropagation();
556
710
  };
711
+ const hideFocusLeavingForWidget = (event) => {
712
+ if (event.relatedTarget !== host) return;
713
+ if (event.composedPath().includes(host)) return;
714
+ event.stopPropagation();
715
+ };
716
+ const hideWidgetFocus = (event) => {
717
+ if (event.composedPath().includes(host)) event.stopImmediatePropagation();
718
+ };
557
719
  HOST_INTERACTION_GUARD_EVENTS.forEach((type) => {
558
720
  shadowRoot.addEventListener(type, stopLeakingToPage, { passive: true });
559
721
  });
722
+ view == null ? void 0 : view.addEventListener("focusout", hideFocusLeavingForWidget, true);
723
+ view == null ? void 0 : view.addEventListener("blur", hideFocusLeavingForWidget, true);
724
+ view == null ? void 0 : view.addEventListener("focus", hideWidgetFocus, true);
560
725
  return () => {
561
726
  HOST_INTERACTION_GUARD_EVENTS.forEach((type) => {
562
727
  shadowRoot.removeEventListener(type, stopLeakingToPage);
563
728
  });
729
+ view == null ? void 0 : view.removeEventListener("focusout", hideFocusLeavingForWidget, true);
730
+ view == null ? void 0 : view.removeEventListener("blur", hideFocusLeavingForWidget, true);
731
+ view == null ? void 0 : view.removeEventListener("focus", hideWidgetFocus, true);
564
732
  };
565
733
  }
566
734
  const RUNTIME_OWNER_MARKER_KEY = "__issueStickerRuntimeOwner";
@@ -899,6 +1067,17 @@ const EllipsisVertical = createLucideIcon$1("EllipsisVertical", [
899
1067
  ["circle", { cx: "12", cy: "5", r: "1", key: "gxeob9" }],
900
1068
  ["circle", { cx: "12", cy: "19", r: "1", key: "lyex9k" }]
901
1069
  ]);
1070
+ /**
1071
+ * @license lucide-react v0.468.0 - ISC
1072
+ *
1073
+ * This source code is licensed under the ISC license.
1074
+ * See the LICENSE file in the root directory of this source tree.
1075
+ */
1076
+ const ExternalLink$1 = createLucideIcon$1("ExternalLink", [
1077
+ ["path", { d: "M15 3h6v6", key: "1q9fwt" }],
1078
+ ["path", { d: "M10 14 21 3", key: "gplh6r" }],
1079
+ ["path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6", key: "a6xqqp" }]
1080
+ ]);
902
1081
  /**
903
1082
  * @license lucide-react v0.468.0 - ISC
904
1083
  *
@@ -989,6 +1168,16 @@ const LocateFixed = createLucideIcon$1("LocateFixed", [
989
1168
  ["circle", { cx: "12", cy: "12", r: "7", key: "fim9np" }],
990
1169
  ["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }]
991
1170
  ]);
1171
+ /**
1172
+ * @license lucide-react v0.468.0 - ISC
1173
+ *
1174
+ * This source code is licensed under the ISC license.
1175
+ * See the LICENSE file in the root directory of this source tree.
1176
+ */
1177
+ const Lock = createLucideIcon$1("Lock", [
1178
+ ["rect", { width: "18", height: "11", x: "3", y: "11", rx: "2", ry: "2", key: "1w4ew1" }],
1179
+ ["path", { d: "M7 11V7a5 5 0 0 1 10 0v4", key: "fwvmzm" }]
1180
+ ]);
992
1181
  /**
993
1182
  * @license lucide-react v0.468.0 - ISC
994
1183
  *
@@ -1482,7 +1671,7 @@ function MultiSelect({
1482
1671
  const contentRef = React.useRef(null);
1483
1672
  const portalContainer = usePortalContainer();
1484
1673
  React.useEffect(() => {
1485
- var _a, _b2;
1674
+ var _a, _b2, _c2, _d2;
1486
1675
  if (!open) {
1487
1676
  return;
1488
1677
  }
@@ -1493,8 +1682,12 @@ function MultiSelect({
1493
1682
  }
1494
1683
  setOpen(false);
1495
1684
  };
1496
- ownerDocument.addEventListener("click", closeOnOutsideClick, true);
1497
- return () => ownerDocument.removeEventListener("click", closeOnOutsideClick, true);
1685
+ const scopes = /* @__PURE__ */ new Set([
1686
+ ownerDocument,
1687
+ ((_c2 = contentRef.current) == null ? void 0 : _c2.getRootNode()) ?? ((_d2 = triggerRef.current) == null ? void 0 : _d2.getRootNode()) ?? ownerDocument
1688
+ ]);
1689
+ scopes.forEach((scope) => scope.addEventListener("click", closeOnOutsideClick, true));
1690
+ return () => scopes.forEach((scope) => scope.removeEventListener("click", closeOnOutsideClick, true));
1498
1691
  }, [open]);
1499
1692
  const emitChange = (next) => {
1500
1693
  onValueChange == null ? void 0 : onValueChange(next);
@@ -3698,8 +3891,8 @@ var QueryObserver = (_g = class extends Subscribable {
3698
3891
  }
3699
3892
  };
3700
3893
  const recreateThenable = () => {
3701
- const pending = __privateSet(this, _currentThenable, nextResult.promise = pendingThenable());
3702
- finalizeThenableIfPossible(pending);
3894
+ const pending2 = __privateSet(this, _currentThenable, nextResult.promise = pendingThenable());
3895
+ finalizeThenableIfPossible(pending2);
3703
3896
  };
3704
3897
  const prevThenable = __privateGet(this, _currentThenable);
3705
3898
  switch (prevThenable.status) {
@@ -5039,6 +5232,231 @@ function useGuardedCallback(callback) {
5039
5232
  }, []);
5040
5233
  return { execute, isExecuting, reset };
5041
5234
  }
5235
+ function isSyncService(value) {
5236
+ return value === "JIRA" || value === "NOTION";
5237
+ }
5238
+ function readUnsupported(metadata) {
5239
+ if (typeof metadata !== "object" || metadata === null) return [];
5240
+ const sync = metadata.description_sync;
5241
+ if (typeof sync !== "object" || sync === null) return [];
5242
+ const list2 = sync.unsupported;
5243
+ if (!Array.isArray(list2)) return [];
5244
+ return list2.filter(
5245
+ (e) => typeof (e == null ? void 0 : e.type) === "string" && typeof (e == null ? void 0 : e.count) === "number"
5246
+ );
5247
+ }
5248
+ function readDescriptionSyncLock(mappings) {
5249
+ for (const mapping of mappings ?? []) {
5250
+ if (!isSyncService(mapping.service_type)) continue;
5251
+ const elements = readUnsupported(mapping.metadata);
5252
+ if (elements.length === 0) continue;
5253
+ return {
5254
+ serviceType: mapping.service_type,
5255
+ blocksEditing: elements.some((e) => e.type === "anchor_missing"),
5256
+ elements
5257
+ };
5258
+ }
5259
+ return null;
5260
+ }
5261
+ function isHiddenBlock(value) {
5262
+ if (typeof value !== "object" || value === null) return false;
5263
+ const { markdown, kinds, viewable } = value;
5264
+ return typeof markdown === "string" && Array.isArray(kinds) && kinds.every((kind) => typeof kind === "string") && typeof viewable === "boolean";
5265
+ }
5266
+ function readHidden(metadata) {
5267
+ if (typeof metadata !== "object" || metadata === null) return void 0;
5268
+ const sync = metadata.description_sync;
5269
+ if (typeof sync !== "object" || sync === null) return void 0;
5270
+ const hidden = sync.hidden;
5271
+ return Array.isArray(hidden) && hidden.every(isHiddenBlock) ? hidden : void 0;
5272
+ }
5273
+ const BLOCK_SEPARATOR = "\n\n";
5274
+ function removeHidden(description, hidden) {
5275
+ let out = description;
5276
+ let from = 0;
5277
+ const cuts = [];
5278
+ const atBoundary = (start, end) => (start === 0 || out.startsWith(BLOCK_SEPARATOR, start - BLOCK_SEPARATOR.length)) && (end === out.length || out.startsWith(BLOCK_SEPARATOR, end));
5279
+ for (const block of hidden) {
5280
+ const piece = block.markdown;
5281
+ if (!piece) continue;
5282
+ let start = out.indexOf(piece, from);
5283
+ while (start !== -1 && !atBoundary(start, start + piece.length)) {
5284
+ const next = start + 1 > out.length ? -1 : out.indexOf(piece, start + 1);
5285
+ start = next > start ? next : -1;
5286
+ }
5287
+ if (start === -1) return null;
5288
+ let end = start + piece.length;
5289
+ if (out.startsWith(BLOCK_SEPARATOR, end)) end += BLOCK_SEPARATOR.length;
5290
+ else if (start >= BLOCK_SEPARATOR.length && out.startsWith(BLOCK_SEPARATOR, start - BLOCK_SEPARATOR.length)) {
5291
+ start -= BLOCK_SEPARATOR.length;
5292
+ }
5293
+ out = out.slice(0, start) + out.slice(end);
5294
+ for (const cut of cuts) cut.at = Math.min(cut.at, start);
5295
+ cuts.push({ block, at: start });
5296
+ from = start;
5297
+ }
5298
+ const lead = out.length - out.trimStart().length;
5299
+ const text2 = out.trim();
5300
+ for (const cut of cuts) cut.at = Math.min(Math.max(cut.at - lead, 0), text2.length);
5301
+ return { text: text2, cuts };
5302
+ }
5303
+ function trimBlankLines(markdown) {
5304
+ return markdown.replace(/^(?:[ \t]*\n)+/, "").replace(/(?:\n[ \t]*)+$/, "");
5305
+ }
5306
+ function splitDescription(description, hidden) {
5307
+ const removed = removeHidden(description, hidden);
5308
+ if (!removed) return null;
5309
+ const { text: text2, cuts } = removed;
5310
+ const pieces = [];
5311
+ const pushText2 = (slice) => {
5312
+ const markdown = trimBlankLines(slice);
5313
+ if (markdown.trim()) pieces.push({ kind: "text", markdown });
5314
+ };
5315
+ let prev = 0;
5316
+ for (const { block, at } of cuts) {
5317
+ pushText2(text2.slice(prev, at));
5318
+ pieces.push({ kind: "hidden", block });
5319
+ prev = at;
5320
+ }
5321
+ pushText2(text2.slice(prev));
5322
+ return pieces;
5323
+ }
5324
+ function editableDescription(description, hidden) {
5325
+ var _a;
5326
+ return ((_a = removeHidden(description, hidden)) == null ? void 0 : _a.text) ?? null;
5327
+ }
5328
+ function editBasisOf(view, description) {
5329
+ const blocks = ((view == null ? void 0 : view.pieces) ?? []).flatMap(
5330
+ (piece) => piece.kind === "hidden" ? [piece.block] : []
5331
+ );
5332
+ if (blocks.length === 0) return [];
5333
+ return editableDescription(description ?? "", blocks) === null ? [] : blocks.map((block) => block.markdown);
5334
+ }
5335
+ function descriptionStateSignature(view, description) {
5336
+ return JSON.stringify([
5337
+ (view == null ? void 0 : view.serviceType) ?? null,
5338
+ editBasisOf(view, description),
5339
+ !!(view == null ? void 0 : view.needsRefresh)
5340
+ ]);
5341
+ }
5342
+ function isSaveRejectionRepeating(sent, fresh) {
5343
+ if ((fresh.description ?? "") !== (sent.description ?? "")) return false;
5344
+ const freshBasis = editBasisOf(
5345
+ readDescriptionView(fresh.issue_external_mappings, fresh.description),
5346
+ fresh.description
5347
+ );
5348
+ return freshBasis.length === sent.basis.length && freshBasis.every((markdown, index2) => markdown === sent.basis[index2]);
5349
+ }
5350
+ function collectHiddenKinds(pieces) {
5351
+ const kinds = /* @__PURE__ */ new Set();
5352
+ for (const piece of pieces) {
5353
+ if (piece.kind === "hidden") for (const kind of piece.block.kinds) kinds.add(kind);
5354
+ }
5355
+ return [...kinds];
5356
+ }
5357
+ function readDescriptionView(mappings, description) {
5358
+ for (const mapping of mappings ?? []) {
5359
+ if (!isSyncService(mapping.service_type)) continue;
5360
+ const unsupported = readUnsupported(mapping.metadata);
5361
+ const hidden = readHidden(mapping.metadata);
5362
+ if (unsupported.length === 0 && !(hidden == null ? void 0 : hidden.length)) continue;
5363
+ const pieces = hidden ? splitDescription(description ?? "", hidden) : null;
5364
+ return {
5365
+ serviceType: mapping.service_type,
5366
+ pieces,
5367
+ // 조각을 못 만들었을 때: 위치를 못 찾은 hidden 이 있거나, hidden 없이 anchor_missing 밖
5368
+ // 보호 요소만 있으면 편집란이 숨길 곳을 모른다.
5369
+ needsRefresh: pieces === null && (!!(hidden == null ? void 0 : hidden.length) || unsupported.some((e) => e.type !== "anchor_missing"))
5370
+ };
5371
+ }
5372
+ return null;
5373
+ }
5374
+ const ELEMENT_NAMES = {
5375
+ panel: "패널",
5376
+ expand: "접기 블록",
5377
+ nestedExpand: "접기 블록",
5378
+ mediaSingle: "첨부 이미지·파일",
5379
+ mediaGroup: "첨부 이미지·파일",
5380
+ media: "첨부 이미지·파일",
5381
+ mediaInline: "첨부 이미지·파일",
5382
+ mention: "멘션",
5383
+ status: "상태 라벨",
5384
+ date: "날짜",
5385
+ emoji: "이모지",
5386
+ blockTaskItem: "체크리스트 항목",
5387
+ syncBlock: "동기화 블록",
5388
+ bodiedSyncBlock: "동기화 블록",
5389
+ textColor: "글자색",
5390
+ underline: "밑줄",
5391
+ subsup: "위첨자·아래첨자",
5392
+ border: "테두리",
5393
+ backgroundColor: "배경색",
5394
+ decisionList: "의사결정 목록",
5395
+ layoutSection: "다단 레이아웃",
5396
+ table: "표",
5397
+ blockCard: "링크 카드",
5398
+ embedCard: "임베드",
5399
+ extension: "매크로",
5400
+ bodiedExtension: "매크로",
5401
+ multiBodiedExtension: "매크로",
5402
+ inlineExtension: "매크로",
5403
+ anchor_missing: "지워진 설명 섹션 제목",
5404
+ // Notion 블록 타입
5405
+ callout: "콜아웃",
5406
+ image: "첨부 이미지·파일",
5407
+ video: "첨부 이미지·파일",
5408
+ audio: "첨부 이미지·파일",
5409
+ file: "첨부 이미지·파일",
5410
+ pdf: "첨부 이미지·파일",
5411
+ bookmark: "북마크",
5412
+ embed: "임베드",
5413
+ column_list: "다단 레이아웃",
5414
+ table_of_contents: "목차",
5415
+ toggle_heading: "토글 제목",
5416
+ synced_block: "동기화 블록",
5417
+ child_page: "하위 페이지",
5418
+ child_database: "하위 데이터베이스",
5419
+ link_preview: "링크 미리보기",
5420
+ breadcrumb: "이동 경로",
5421
+ link_to_page: "페이지 링크",
5422
+ template: "템플릿 버튼",
5423
+ code: "코드 블록"
5424
+ };
5425
+ const ELEMENT_NAME_PREFIXES = [
5426
+ ["rich_text:mention:", "멘션"],
5427
+ ["rich_text:", "지원하지 않는 서식"],
5428
+ ["unsupported:", "지원하지 않는 블록"]
5429
+ ];
5430
+ function knownElementName(type) {
5431
+ var _a;
5432
+ if (Object.prototype.hasOwnProperty.call(ELEMENT_NAMES, type)) return ELEMENT_NAMES[type];
5433
+ return (_a = ELEMENT_NAME_PREFIXES.find(([prefix]) => type.startsWith(prefix))) == null ? void 0 : _a[1];
5434
+ }
5435
+ const UNKNOWN_ELEMENT_NAME = "지원하지 않는 블록";
5436
+ function describeUnsupportedElement(type) {
5437
+ return knownElementName(type) ?? UNKNOWN_ELEMENT_NAME;
5438
+ }
5439
+ const ERROR_MESSAGES = {
5440
+ DESCRIPTION_EXTERNAL_CHANGED: "연동된 서비스에서 설명이 먼저 바뀌어 최신 내용을 불러왔습니다. 내용을 확인하고 다시 저장해주세요.",
5441
+ DESCRIPTION_NOT_REPRESENTABLE: "설명에 연동된 서비스가 표현할 수 없는 내용이 있어 저장하지 못했습니다. 해당 내용을 빼고 다시 저장해주세요.",
5442
+ SERVICE_UNAVAILABLE: "연동된 서비스에 연결하지 못했습니다. 잠시 후 다시 시도해주세요."
5443
+ };
5444
+ const SERVICE_NAMES = { JIRA: "Jira", NOTION: "Notion" };
5445
+ function describeSyncService(serviceType) {
5446
+ return SERVICE_NAMES[serviceType];
5447
+ }
5448
+ function describeHiddenKinds(kinds) {
5449
+ return [...new Set(kinds.map(describeUnsupportedElement))].join(", ");
5450
+ }
5451
+ const DESCRIPTION_REFRESH_FAILED_MESSAGE = "설명을 다시 불러오지 못했습니다.";
5452
+ const DESCRIPTION_STRIPPED_SAVE_REJECTED_MESSAGE = "숨긴 내용을 뺀 설명을 저장하지 못했습니다.";
5453
+ function descriptionSyncErrorMessage(code2, serviceType) {
5454
+ if (code2 === "DESCRIPTION_SYNC_LOCKED") {
5455
+ const service = serviceType ? SERVICE_NAMES[serviceType] : "연동된 서비스";
5456
+ return `${service}에서만 고칠 수 있는 부분을 바꿨거나 설명 섹션의 제목이 지워져 저장하지 못했습니다. 바꾼 부분은 원래대로 되돌리고, 지워진 제목은 ${service}에서 되살린 뒤 다시 저장해주세요.`;
5457
+ }
5458
+ return code2 && Object.prototype.hasOwnProperty.call(ERROR_MESSAGES, code2) ? ERROR_MESSAGES[code2] : null;
5459
+ }
5042
5460
  function createViewportBounds() {
5043
5461
  return {
5044
5462
  width: window.innerWidth,
@@ -5103,16 +5521,19 @@ class WebPlatform {
5103
5521
  this.screenshot = {
5104
5522
  capture: async (options) => {
5105
5523
  const excludeSelectors = (options == null ? void 0 : options.excludeSelectors) ?? [];
5524
+ const maskSelectors = (options == null ? void 0 : options.maskSelectors) ?? [];
5106
5525
  if (options == null ? void 0 : options.element) {
5107
- return captureWithModernScreenshot({
5526
+ return captureScreenshotWithMasks({
5108
5527
  element: options.element,
5109
- excludeSelectors
5528
+ excludeSelectors,
5529
+ maskSelectors
5110
5530
  });
5111
5531
  }
5112
5532
  const viewportBounds = createViewportBounds();
5113
- return captureWithModernScreenshot({
5533
+ return captureScreenshotWithMasks({
5114
5534
  element: document.documentElement,
5115
5535
  excludeSelectors,
5536
+ maskSelectors,
5116
5537
  width: viewportBounds.width,
5117
5538
  height: viewportBounds.height
5118
5539
  });
@@ -5127,149 +5548,44 @@ class WebPlatform {
5127
5548
  this.eventBus = eventBus;
5128
5549
  }
5129
5550
  }
5130
- class EventBus {
5131
- constructor() {
5132
- this.listeners = /* @__PURE__ */ new Map();
5133
- }
5134
- on(event, callback) {
5135
- if (!this.listeners.has(event)) {
5136
- this.listeners.set(event, /* @__PURE__ */ new Set());
5137
- }
5138
- this.listeners.get(event).add(callback);
5139
- return () => this.off(event, callback);
5140
- }
5141
- off(event, callback) {
5142
- var _a;
5143
- (_a = this.listeners.get(event)) == null ? void 0 : _a.delete(callback);
5144
- }
5145
- emit(event, ...args) {
5146
- var _a, _b2;
5147
- (_a = this.listeners.get(event)) == null ? void 0 : _a.forEach((cb) => cb(...args));
5148
- (_b2 = this.listeners.get("*")) == null ? void 0 : _b2.forEach((cb) => cb(event, ...args));
5149
- }
5150
- once(event, callback) {
5151
- const wrapper = (...args) => {
5152
- this.off(event, wrapper);
5153
- callback(...args);
5154
- };
5155
- return this.on(event, wrapper);
5156
- }
5157
- removeAllListeners() {
5158
- this.listeners.clear();
5159
- }
5551
+ function isHttpStatusError(error) {
5552
+ return typeof error === "object" && error !== null && typeof error.status === "number";
5160
5553
  }
5161
- const DEFAULT_FLAGS = {
5162
- networkLogs: true,
5163
- consoleLogs: true,
5164
- screenshot: true,
5165
- screenRecording: true,
5166
- stickers: true,
5167
- keyboardShortcuts: true,
5168
- jiraIntegration: false,
5169
- fab: true
5170
- };
5171
- class FeatureFlags {
5172
- constructor(overrides) {
5173
- this.flags = { ...DEFAULT_FLAGS, ...overrides };
5174
- }
5175
- get(name2) {
5176
- return this.flags[name2];
5177
- }
5178
- set(name2, enabled) {
5179
- this.flags[name2] = enabled;
5180
- }
5181
- getAll() {
5182
- return { ...this.flags };
5183
- }
5554
+ const MAX_RETRIES = 3;
5555
+ const MAX_BACKOFF_MS = 3e4;
5556
+ const MAX_RETRY_AFTER_MS = 6e4;
5557
+ function parseRetryAfterMs(value, now2 = Date.now()) {
5558
+ const trimmed = value == null ? void 0 : value.trim();
5559
+ if (!trimmed) return void 0;
5560
+ if (/^\d+$/.test(trimmed)) return Number(trimmed) * 1e3;
5561
+ const date = Date.parse(trimmed);
5562
+ return Number.isNaN(date) ? void 0 : Math.max(0, date - now2);
5184
5563
  }
5185
- const _SDKManager = class _SDKManager {
5186
- constructor() {
5187
- this._config = null;
5188
- this._platform = null;
5189
- this._eventBus = null;
5190
- this._featureFlags = null;
5191
- this._initialized = false;
5192
- }
5193
- static getInstance() {
5194
- if (!_SDKManager.instance) {
5195
- _SDKManager.instance = new _SDKManager();
5196
- }
5197
- return _SDKManager.instance;
5198
- }
5199
- init(config) {
5200
- if (this._initialized) {
5201
- this.updateConfig(config);
5202
- return;
5203
- }
5204
- this._config = config;
5205
- this._eventBus = new EventBus();
5206
- this._platform = new WebPlatform(this._eventBus);
5207
- this._featureFlags = new FeatureFlags({});
5208
- this._initialized = true;
5209
- }
5210
- updateConfig(config) {
5211
- if (!this._initialized) {
5212
- this.init(config);
5213
- return;
5214
- }
5215
- this._config = config;
5216
- }
5217
- destroy() {
5218
- var _a;
5219
- (_a = this._eventBus) == null ? void 0 : _a.removeAllListeners();
5220
- this._config = null;
5221
- this._platform = null;
5222
- this._eventBus = null;
5223
- this._featureFlags = null;
5224
- this._initialized = false;
5225
- }
5226
- isInitialized() {
5227
- return this._initialized;
5228
- }
5229
- get config() {
5230
- if (!this._config) throw new Error("SDK not initialized");
5231
- return this._config;
5232
- }
5233
- get platform() {
5234
- if (!this._platform) throw new Error("SDK not initialized");
5235
- return this._platform;
5236
- }
5237
- get eventBus() {
5238
- if (!this._eventBus) throw new Error("SDK not initialized");
5239
- return this._eventBus;
5240
- }
5241
- get featureFlags() {
5242
- if (!this._featureFlags) throw new Error("SDK not initialized");
5243
- return this._featureFlags;
5244
- }
5245
- static resetInstance() {
5246
- if (_SDKManager.instance) {
5247
- _SDKManager.instance.destroy();
5248
- _SDKManager.instance = null;
5249
- }
5250
- }
5251
- };
5252
- _SDKManager.instance = null;
5253
- let SDKManager = _SDKManager;
5254
- const pendingListeners = /* @__PURE__ */ new Map();
5255
- function addPendingSdkEventListener(event, callback) {
5256
- if (!pendingListeners.has(event)) {
5257
- pendingListeners.set(event, /* @__PURE__ */ new Set());
5258
- }
5259
- pendingListeners.get(event).add(callback);
5564
+ function retryAfterMsOf(response) {
5565
+ var _a;
5566
+ return parseRetryAfterMs((_a = response.headers) == null ? void 0 : _a.get("Retry-After"));
5260
5567
  }
5261
- function removePendingSdkEventListener(event, callback) {
5262
- var _a, _b2;
5263
- (_a = pendingListeners.get(event)) == null ? void 0 : _a.delete(callback);
5264
- if (((_b2 = pendingListeners.get(event)) == null ? void 0 : _b2.size) === 0) {
5265
- pendingListeners.delete(event);
5568
+ function jitteredBackoffMs(failureCount, capMs = MAX_BACKOFF_MS) {
5569
+ return Math.random() * Math.min(capMs, 1e3 * 2 ** failureCount);
5570
+ }
5571
+ function statusOf(error) {
5572
+ return isHttpStatusError(error) ? error.status : 0;
5573
+ }
5574
+ function retryAfterOf(error) {
5575
+ return isHttpStatusError(error) ? error.retryAfterMs : void 0;
5576
+ }
5577
+ function queryRetry(failureCount, error) {
5578
+ const status = statusOf(error);
5579
+ if (status === 429) {
5580
+ const retryAfterMs = retryAfterOf(error);
5581
+ return failureCount < 1 && retryAfterMs !== void 0 && retryAfterMs <= MAX_RETRY_AFTER_MS;
5266
5582
  }
5583
+ if (status >= 400 && status < 500 && status !== 408) return false;
5584
+ return failureCount < MAX_RETRIES;
5267
5585
  }
5268
- function flushPendingSdkEventListeners(eventBus) {
5269
- pendingListeners.forEach((listeners2, event) => {
5270
- listeners2.forEach((callback) => eventBus.on(event, callback));
5271
- });
5272
- pendingListeners.clear();
5586
+ function queryRetryDelay(failureCount, error) {
5587
+ const retryAfterMs = retryAfterOf(error);
5588
+ return retryAfterMs !== void 0 ? Math.min(retryAfterMs, MAX_RETRY_AFTER_MS) : jitteredBackoffMs(failureCount);
5273
5589
  }
5274
5590
  function readApiBaseUrlFromEnv() {
5275
5591
  if (typeof process !== "undefined" && process.env && Object.prototype.hasOwnProperty.call(process.env, "VITE_API_BASE_URL")) {
@@ -5371,10 +5687,11 @@ async function clearToken() {
5371
5687
  tokenMemoryCache = null;
5372
5688
  }
5373
5689
  class ApiError extends Error {
5374
- constructor(message, status, data) {
5690
+ constructor(message, status, data, retryAfterMs) {
5375
5691
  super(message);
5376
5692
  this.status = status;
5377
5693
  this.data = data;
5694
+ this.retryAfterMs = retryAfterMs;
5378
5695
  this.name = "ApiError";
5379
5696
  }
5380
5697
  }
@@ -5443,7 +5760,8 @@ async function requestResponse(endpoint, options = {}) {
5443
5760
  throw new ApiError(
5444
5761
  extractErrorMessage(json.error) || json.message || `요청 실패: ${response.status}`,
5445
5762
  response.status,
5446
- json
5763
+ json,
5764
+ retryAfterMsOf(response)
5447
5765
  );
5448
5766
  }
5449
5767
  return json;
@@ -5506,7 +5824,8 @@ async function uploadRequest(endpoint, formData, skipAuth = false) {
5506
5824
  throw new ApiError(
5507
5825
  extractErrorMessage(json.error) || json.message || `업로드 실패: ${response.status}`,
5508
5826
  response.status,
5509
- json
5827
+ json,
5828
+ retryAfterMsOf(response)
5510
5829
  );
5511
5830
  }
5512
5831
  return json.data;
@@ -5544,6 +5863,456 @@ const apiClient = {
5544
5863
  return uploadRequest(endpoint, formData, skipAuth);
5545
5864
  }
5546
5865
  };
5866
+ const queryClient = new QueryClient({
5867
+ defaultOptions: {
5868
+ queries: {
5869
+ staleTime: 5 * 60 * 1e3,
5870
+ // 5분
5871
+ gcTime: 10 * 60 * 1e3,
5872
+ // 10분 (v5에서는 cacheTime 대신 gcTime 사용)
5873
+ refetchOnWindowFocus: true,
5874
+ refetchOnReconnect: true,
5875
+ retry: queryRetry,
5876
+ retryDelay: queryRetryDelay
5877
+ },
5878
+ mutations: {
5879
+ retry: 0
5880
+ }
5881
+ }
5882
+ });
5883
+ async function uploadSignedMedia(file, mediaType, issueId) {
5884
+ return uploadSignedMediaWithApi(
5885
+ {
5886
+ post: (endpoint, body) => apiClient.post(endpoint, body),
5887
+ directUpload
5888
+ },
5889
+ file,
5890
+ mediaType,
5891
+ { issueId }
5892
+ );
5893
+ }
5894
+ function issuesEndpoint() {
5895
+ return "/api/sdk/issues";
5896
+ }
5897
+ function guestOwnerQuery() {
5898
+ var _a;
5899
+ const mode = getSdkMode();
5900
+ if (mode !== "public") return "";
5901
+ return buildGuestOwnerQuery(mode, (_a = SDKManager.getInstance().config.user) == null ? void 0 : _a.id);
5902
+ }
5903
+ let issuesCache = null;
5904
+ const CACHE_DURATION_MS = 30 * 1e3;
5905
+ const STICKER_PAGE_SIZE = 500;
5906
+ const STICKER_FETCH_LIMIT = 2e3;
5907
+ function getFilterKey(filters) {
5908
+ var _a;
5909
+ const normalizeArray = (value) => {
5910
+ if (value === "all" || !value) return "all";
5911
+ if (Array.isArray(value)) {
5912
+ return value.length === 0 ? "all" : [...value].sort().join(",");
5913
+ }
5914
+ return String(value);
5915
+ };
5916
+ const normalizeRecord = (record) => {
5917
+ if (!record) return "";
5918
+ const entries = Object.entries(record).map(([k, v]) => [k, [...v].sort()]).sort(([a], [b]) => a.localeCompare(b));
5919
+ return JSON.stringify(entries);
5920
+ };
5921
+ return JSON.stringify({
5922
+ sdkMode: getSdkMode() || "public",
5923
+ url: normalizeArray(filters.url),
5924
+ projectId: filters.projectId || "all",
5925
+ externalUserId: filters.externalUserId || "",
5926
+ search: ((_a = filters.search) == null ? void 0 : _a.trim()) || "",
5927
+ dateFrom: filters.dateFrom || "",
5928
+ dateTo: filters.dateTo || "",
5929
+ page: filters.page ?? 0,
5930
+ pageSize: filters.pageSize ?? 0,
5931
+ custom: normalizeRecord(filters.customFieldValues),
5932
+ jira: normalizeRecord(filters.jiraFieldValues),
5933
+ notion: normalizeRecord(filters.notionFieldValues),
5934
+ sortOrder: filters.sortOrder || ""
5935
+ });
5936
+ }
5937
+ function invalidateIssuesCache() {
5938
+ issuesCache = null;
5939
+ }
5940
+ function recordToFilterArray(record, keyFieldName) {
5941
+ if (!record) return null;
5942
+ const entries = Object.entries(record).filter(([, values]) => values.length > 0);
5943
+ if (entries.length === 0) return null;
5944
+ return entries.map(
5945
+ ([key, values]) => ({ [keyFieldName]: key, values })
5946
+ );
5947
+ }
5948
+ function buildFilterQueryParams(filters) {
5949
+ var _a;
5950
+ const params = new URLSearchParams();
5951
+ if (filters.projectId) {
5952
+ params.set("project_id", filters.projectId);
5953
+ }
5954
+ if (filters.url && filters.url !== "all") {
5955
+ if (Array.isArray(filters.url) && filters.url.length > 0) {
5956
+ params.set("url", filters.url.join(","));
5957
+ } else if (typeof filters.url === "string") {
5958
+ params.set("url", filters.url);
5959
+ }
5960
+ }
5961
+ const search2 = (_a = filters.search) == null ? void 0 : _a.trim();
5962
+ if (search2) {
5963
+ params.set("search", search2);
5964
+ }
5965
+ if (filters.dateFrom) {
5966
+ params.set("date_from", filters.dateFrom);
5967
+ }
5968
+ if (filters.dateTo) {
5969
+ params.set("date_to", filters.dateTo);
5970
+ }
5971
+ const customArr = recordToFilterArray(filters.customFieldValues, "fieldDefinitionId");
5972
+ if (customArr) {
5973
+ params.set("custom_field_filters", JSON.stringify(customArr));
5974
+ }
5975
+ const jiraArr = recordToFilterArray(filters.jiraFieldValues, "fieldName");
5976
+ if (jiraArr) {
5977
+ params.set("jira_field_filters", JSON.stringify(jiraArr));
5978
+ }
5979
+ const notionArr = recordToFilterArray(filters.notionFieldValues, "fieldName");
5980
+ if (notionArr) {
5981
+ params.set("notion_field_filters", JSON.stringify(notionArr));
5982
+ }
5983
+ if (filters.externalUserId) {
5984
+ params.set("external_user_id", filters.externalUserId);
5985
+ }
5986
+ if (typeof filters.page === "number" && filters.page > 0) {
5987
+ params.set("page", String(filters.page));
5988
+ }
5989
+ if (typeof filters.pageSize === "number" && filters.pageSize > 0) {
5990
+ params.set("page_size", String(filters.pageSize));
5991
+ }
5992
+ if (filters.sortOrder) {
5993
+ params.set("sort_order", filters.sortOrder);
5994
+ }
5995
+ const queryString = params.toString();
5996
+ return queryString ? `?${queryString}` : "";
5997
+ }
5998
+ const issueService = {
5999
+ async createIssue(issueData) {
6000
+ const sanitizedData = sanitizeString(issueData);
6001
+ const screenshotUploadIds = (sanitizedData.screenshot || []).map((url) => getRegisteredUploadId(url, "screenshot")).filter((id2) => typeof id2 === "string" && id2.length > 0);
6002
+ const videoUploadId = sanitizedData.video ? getRegisteredUploadId(sanitizedData.video, "video") : void 0;
6003
+ if (screenshotUploadIds.length > 0) {
6004
+ sanitizedData.screenshot_upload_ids = screenshotUploadIds;
6005
+ }
6006
+ if (videoUploadId) {
6007
+ sanitizedData.video_upload_id = videoUploadId;
6008
+ }
6009
+ const issue = await apiClient.post(issuesEndpoint(), sanitizedData);
6010
+ invalidateIssuesCache();
6011
+ return issue;
6012
+ },
6013
+ async getIssues(filters = {}, options = {}) {
6014
+ if (!filters.projectId) {
6015
+ throw new Error("프로젝트 ID가 필요합니다. 로그인 상태를 확인해주세요.");
6016
+ }
6017
+ const filterKey = getFilterKey(filters);
6018
+ const now2 = Date.now();
6019
+ if (!options.forceRefresh && issuesCache && issuesCache.filterKey === filterKey && now2 - issuesCache.timestamp < CACHE_DURATION_MS) {
6020
+ return issuesCache.data;
6021
+ }
6022
+ const queryParams = buildFilterQueryParams(filters);
6023
+ const { items: issues = [] } = await apiClient.get(
6024
+ `${issuesEndpoint()}${queryParams}`
6025
+ );
6026
+ issuesCache = {
6027
+ data: issues,
6028
+ timestamp: now2,
6029
+ filterKey
6030
+ };
6031
+ return issues;
6032
+ },
6033
+ async getIssueStickers(filters = {}, options = {}) {
6034
+ var _a, _b2;
6035
+ if (!filters.projectId) {
6036
+ throw new Error("프로젝트 ID가 필요합니다. 로그인 상태를 확인해주세요.");
6037
+ }
6038
+ const filterKey = `${getFilterKey(filters)}:stickers`;
6039
+ const now2 = Date.now();
6040
+ if (!options.forceRefresh && issuesCache && issuesCache.filterKey === filterKey && now2 - issuesCache.timestamp < CACHE_DURATION_MS) {
6041
+ return issuesCache.data;
6042
+ }
6043
+ const issues = [];
6044
+ let page = 1;
6045
+ let hasMore = true;
6046
+ while (hasMore && issues.length <= STICKER_FETCH_LIMIT) {
6047
+ const response = await apiClient.getResponse(
6048
+ `${issuesEndpoint()}/stickers${buildFilterQueryParams({
6049
+ ...filters,
6050
+ page,
6051
+ pageSize: STICKER_PAGE_SIZE
6052
+ })}`
6053
+ );
6054
+ const pageItems = ((_a = response.data) == null ? void 0 : _a.items) ?? [];
6055
+ issues.push(...pageItems);
6056
+ hasMore = Boolean((_b2 = response.data) == null ? void 0 : _b2.hasMore) && pageItems.length > 0;
6057
+ page += 1;
6058
+ }
6059
+ issuesCache = {
6060
+ data: issues,
6061
+ timestamp: now2,
6062
+ filterKey
6063
+ };
6064
+ return issues;
6065
+ },
6066
+ invalidateCache() {
6067
+ invalidateIssuesCache();
6068
+ },
6069
+ // 계약 기준: 백엔드 GET /api/sdk/issues/:id 는 back#101부터 읽기-시점-스크럽된
6070
+ // llm_prompt 를 상세 응답에 포함한다(목록 응답에는 없음) — 스코프를 이 반환 타입에만 한정한다.
6071
+ async getIssue(id2) {
6072
+ return apiClient.get(
6073
+ `${issuesEndpoint()}/${id2}${guestOwnerQuery()}`
6074
+ );
6075
+ },
6076
+ async updateIssue(id2, updates) {
6077
+ const sanitizedUpdates = sanitizeString(updates);
6078
+ const issue = await apiClient.put(
6079
+ `${issuesEndpoint()}/${id2}${guestOwnerQuery()}`,
6080
+ sanitizedUpdates
6081
+ );
6082
+ invalidateIssuesCache();
6083
+ return issue;
6084
+ },
6085
+ /**
6086
+ * Jira·Notion 의 현재 설명으로 이슈 설명과 숨길 묶음 기록을 다시 만든다.
6087
+ *
6088
+ * 응답(IssueMutationData)에는 연동 매핑이 없어 화면이 숨길 위치를 다시 판단할 수 없다. 호출부가
6089
+ * 이슈를 다시 조회해야 한다. 게스트 소유자 쿼리는 형제 메서드와 같은 규칙이다.
6090
+ */
6091
+ async refreshIssueDescription(id2) {
6092
+ const issue = await apiClient.post(
6093
+ `${issuesEndpoint()}/${encodeURIComponent(id2)}/description/refresh${guestOwnerQuery()}`,
6094
+ {}
6095
+ );
6096
+ invalidateIssuesCache();
6097
+ return issue;
6098
+ },
6099
+ /**
6100
+ * 재생성. 스펙상 세 채널 모두에서 되고 역할과 무관하다 — 검사는 역할이 아니라
6101
+ * 그 이슈에 접근할 수 있는지다(백엔드가 판정한다).
6102
+ *
6103
+ * 응답으로 갱신된 이슈가 오지만 상태는 다시 pending 이다. 호출부는 응답을
6104
+ * 그대로 쓰지 말고 폴링으로 이어받아야 한다.
6105
+ */
6106
+ async regenerateIssue(id2) {
6107
+ const issue = await apiClient.post(
6108
+ `${issuesEndpoint()}/${encodeURIComponent(id2)}/regenerate${guestOwnerQuery()}`,
6109
+ {}
6110
+ );
6111
+ invalidateIssuesCache();
6112
+ return issue;
6113
+ },
6114
+ async deleteIssue(id2, options) {
6115
+ const endpoint = `${issuesEndpoint()}/${id2}${guestOwnerQuery()}`;
6116
+ if (options == null ? void 0 : options.deleteExternal) {
6117
+ await apiClient.delete(endpoint, {
6118
+ body: { deleteExternal: true }
6119
+ });
6120
+ } else {
6121
+ await apiClient.delete(endpoint);
6122
+ }
6123
+ invalidateIssuesCache();
6124
+ }
6125
+ };
6126
+ function invalidateIssueQueries(issueId) {
6127
+ issueService.invalidateCache();
6128
+ queryClient.invalidateQueries({ queryKey: ["contentIssues"] });
6129
+ queryClient.invalidateQueries({ queryKey: ["issue", issueId] });
6130
+ }
6131
+ function notifyIssueUpdated(issueId) {
6132
+ SDKManager.getInstance().eventBus.emit("issue:updated", issueId);
6133
+ }
6134
+ function applyIssueMutationEffects(issueId) {
6135
+ invalidateIssueQueries(issueId);
6136
+ notifyIssueUpdated(issueId);
6137
+ }
6138
+ let queues = null;
6139
+ const toaster = createIssueUploadToaster();
6140
+ function getQueues() {
6141
+ if (queues) return queues;
6142
+ queues = createIssueUploadQueues({
6143
+ api: createMediaUploadApi({ post: (endpoint, body) => apiClient.post(endpoint, body) }),
6144
+ clientInfo: "issue-sticker-sdk",
6145
+ videoStore: createIndexedDbPendingUploadStore(VIDEO_UPLOAD_DB_NAME),
6146
+ attachmentStore: createIndexedDbPendingUploadStore(ATTACHMENT_UPLOAD_DB_NAME),
6147
+ loadTus: loadTusClient,
6148
+ onEvent: (event) => {
6149
+ if (event.type !== "failed") invalidateIssueQueries(event.issueId);
6150
+ },
6151
+ onNotice: (notice) => toaster.show(notice, retryWidgetIssueUploads)
6152
+ });
6153
+ return queues;
6154
+ }
6155
+ function getWidgetVideoUploadQueue() {
6156
+ return getQueues().video;
6157
+ }
6158
+ function getWidgetAttachmentUploadQueue() {
6159
+ return getQueues().attachments;
6160
+ }
6161
+ function resumeWidgetIssueUploads(projectKey) {
6162
+ return getQueues().resume([projectKey]);
6163
+ }
6164
+ function retryWidgetIssueUploads(issueId) {
6165
+ queues == null ? void 0 : queues.retry(issueId);
6166
+ }
6167
+ function stopWidgetUploadQueues() {
6168
+ queues == null ? void 0 : queues.stop();
6169
+ queues = null;
6170
+ }
6171
+ class EventBus {
6172
+ constructor() {
6173
+ this.listeners = /* @__PURE__ */ new Map();
6174
+ }
6175
+ on(event, callback) {
6176
+ if (!this.listeners.has(event)) {
6177
+ this.listeners.set(event, /* @__PURE__ */ new Set());
6178
+ }
6179
+ this.listeners.get(event).add(callback);
6180
+ return () => this.off(event, callback);
6181
+ }
6182
+ off(event, callback) {
6183
+ var _a;
6184
+ (_a = this.listeners.get(event)) == null ? void 0 : _a.delete(callback);
6185
+ }
6186
+ emit(event, ...args) {
6187
+ var _a, _b2;
6188
+ (_a = this.listeners.get(event)) == null ? void 0 : _a.forEach((cb) => cb(...args));
6189
+ (_b2 = this.listeners.get("*")) == null ? void 0 : _b2.forEach((cb) => cb(event, ...args));
6190
+ }
6191
+ once(event, callback) {
6192
+ const wrapper = (...args) => {
6193
+ this.off(event, wrapper);
6194
+ callback(...args);
6195
+ };
6196
+ return this.on(event, wrapper);
6197
+ }
6198
+ removeAllListeners() {
6199
+ this.listeners.clear();
6200
+ }
6201
+ }
6202
+ const DEFAULT_FLAGS = {
6203
+ networkLogs: true,
6204
+ consoleLogs: true,
6205
+ screenshot: true,
6206
+ screenRecording: true,
6207
+ stickers: true,
6208
+ keyboardShortcuts: true,
6209
+ jiraIntegration: false,
6210
+ fab: true
6211
+ };
6212
+ class FeatureFlags {
6213
+ constructor(overrides) {
6214
+ this.flags = { ...DEFAULT_FLAGS, ...overrides };
6215
+ }
6216
+ get(name2) {
6217
+ return this.flags[name2];
6218
+ }
6219
+ set(name2, enabled) {
6220
+ this.flags[name2] = enabled;
6221
+ }
6222
+ getAll() {
6223
+ return { ...this.flags };
6224
+ }
6225
+ }
6226
+ const _SDKManager = class _SDKManager {
6227
+ constructor() {
6228
+ this._config = null;
6229
+ this._platform = null;
6230
+ this._eventBus = null;
6231
+ this._featureFlags = null;
6232
+ this._initialized = false;
6233
+ }
6234
+ static getInstance() {
6235
+ if (!_SDKManager.instance) {
6236
+ _SDKManager.instance = new _SDKManager();
6237
+ }
6238
+ return _SDKManager.instance;
6239
+ }
6240
+ init(config) {
6241
+ if (this._initialized) {
6242
+ this.updateConfig(config);
6243
+ return;
6244
+ }
6245
+ this._config = config;
6246
+ this._eventBus = new EventBus();
6247
+ this._platform = new WebPlatform(this._eventBus);
6248
+ this._featureFlags = new FeatureFlags({});
6249
+ this._initialized = true;
6250
+ }
6251
+ updateConfig(config) {
6252
+ if (!this._initialized) {
6253
+ this.init(config);
6254
+ return;
6255
+ }
6256
+ this._config = config;
6257
+ }
6258
+ destroy() {
6259
+ var _a;
6260
+ stopWidgetUploadQueues();
6261
+ (_a = this._eventBus) == null ? void 0 : _a.removeAllListeners();
6262
+ this._config = null;
6263
+ this._platform = null;
6264
+ this._eventBus = null;
6265
+ this._featureFlags = null;
6266
+ this._initialized = false;
6267
+ }
6268
+ isInitialized() {
6269
+ return this._initialized;
6270
+ }
6271
+ get config() {
6272
+ if (!this._config) throw new Error("SDK not initialized");
6273
+ return this._config;
6274
+ }
6275
+ get platform() {
6276
+ if (!this._platform) throw new Error("SDK not initialized");
6277
+ return this._platform;
6278
+ }
6279
+ get eventBus() {
6280
+ if (!this._eventBus) throw new Error("SDK not initialized");
6281
+ return this._eventBus;
6282
+ }
6283
+ get featureFlags() {
6284
+ if (!this._featureFlags) throw new Error("SDK not initialized");
6285
+ return this._featureFlags;
6286
+ }
6287
+ static resetInstance() {
6288
+ if (_SDKManager.instance) {
6289
+ _SDKManager.instance.destroy();
6290
+ _SDKManager.instance = null;
6291
+ }
6292
+ }
6293
+ };
6294
+ _SDKManager.instance = null;
6295
+ let SDKManager = _SDKManager;
6296
+ const pendingListeners = /* @__PURE__ */ new Map();
6297
+ function addPendingSdkEventListener(event, callback) {
6298
+ if (!pendingListeners.has(event)) {
6299
+ pendingListeners.set(event, /* @__PURE__ */ new Set());
6300
+ }
6301
+ pendingListeners.get(event).add(callback);
6302
+ }
6303
+ function removePendingSdkEventListener(event, callback) {
6304
+ var _a, _b2;
6305
+ (_a = pendingListeners.get(event)) == null ? void 0 : _a.delete(callback);
6306
+ if (((_b2 = pendingListeners.get(event)) == null ? void 0 : _b2.size) === 0) {
6307
+ pendingListeners.delete(event);
6308
+ }
6309
+ }
6310
+ function flushPendingSdkEventListeners(eventBus) {
6311
+ pendingListeners.forEach((listeners2, event) => {
6312
+ listeners2.forEach((callback) => eventBus.on(event, callback));
6313
+ });
6314
+ pendingListeners.clear();
6315
+ }
5547
6316
  let session = null;
5548
6317
  function resolveApiBaseUrl() {
5549
6318
  try {
@@ -5710,25 +6479,6 @@ const createShadowRoot = (hostElement, cssTexts = []) => {
5710
6479
  const getPortalContainer = () => {
5711
6480
  return _portalTarget || document.body;
5712
6481
  };
5713
- const queryClient = new QueryClient({
5714
- defaultOptions: {
5715
- queries: {
5716
- staleTime: 5 * 60 * 1e3,
5717
- // 5분
5718
- gcTime: 10 * 60 * 1e3,
5719
- // 10분 (v5에서는 cacheTime 대신 gcTime 사용)
5720
- refetchOnWindowFocus: true,
5721
- refetchOnReconnect: true,
5722
- retry: 3,
5723
- retryDelay: (attemptIndex) => Math.min(1e3 * 2 ** attemptIndex, 3e4)
5724
- },
5725
- mutations: {
5726
- retry: 1,
5727
- // Mutation은 1번만 재시도
5728
- retryDelay: 1e3
5729
- }
5730
- }
5731
- });
5732
6482
  /**
5733
6483
  * @license lucide-react v0.562.0 - ISC
5734
6484
  *
@@ -5904,6 +6654,55 @@ const authService = {
5904
6654
  await apiClient.post("/api/auth/logout", {});
5905
6655
  }
5906
6656
  };
6657
+ function createWidgetSource() {
6658
+ return createMaskingPolicySource({
6659
+ // 키에 묶인 프로젝트의 유효 정책만 내려온다(계약 1장). issues:create 권한이 없으면 403 으로
6660
+ // 던져져 정책을 쓸 수 없는 상태가 된다. 빈 정책으로 바꾸지 않는다.
6661
+ fetchSnapshot: () => apiClient.get("/api/sdk/masking-policy", { cache: "no-store" }),
6662
+ cache: createMemoryMaskingPolicyCache()
6663
+ });
6664
+ }
6665
+ let current = createWidgetSource();
6666
+ let lastRefreshed = null;
6667
+ let lastRefreshedAt = null;
6668
+ let pending = null;
6669
+ function resetWidgetMaskingPolicy() {
6670
+ current = createWidgetSource();
6671
+ lastRefreshed = null;
6672
+ lastRefreshedAt = null;
6673
+ pending = null;
6674
+ }
6675
+ async function getMaskingPolicySnapshot() {
6676
+ const fresh = lastRefreshed;
6677
+ const stored = await current.getMaskingPolicySnapshot().catch((error) => {
6678
+ if (!fresh) throw error;
6679
+ return null;
6680
+ });
6681
+ return stored && (!fresh || stored.version >= fresh.version) ? stored : fresh;
6682
+ }
6683
+ async function refreshMaskingPolicySnapshot(options) {
6684
+ const source = current;
6685
+ const request2 = source.refreshMaskingPolicySnapshot(options);
6686
+ pending = request2;
6687
+ const snapshot = await request2;
6688
+ if (source === current && (!lastRefreshed || snapshot.version >= lastRefreshed.version)) {
6689
+ lastRefreshed = snapshot;
6690
+ lastRefreshedAt = Date.now();
6691
+ }
6692
+ return snapshot;
6693
+ }
6694
+ function pendingWidgetMaskingPolicyRefresh() {
6695
+ return pending;
6696
+ }
6697
+ function widgetMaskingPolicyRefreshedWithin(maxAgeMs) {
6698
+ if (lastRefreshedAt === null) return false;
6699
+ const age = Date.now() - lastRefreshedAt;
6700
+ return age >= 0 && age <= maxAgeMs;
6701
+ }
6702
+ const widgetMaskingPolicySource = {
6703
+ getMaskingPolicySnapshot,
6704
+ refreshMaskingPolicySnapshot
6705
+ };
5907
6706
  const BRIDGE_READY = "ISSUE_STICKER_SDK_AUTH_BRIDGE_READY";
5908
6707
  const BRIDGE_REQUEST = "ISSUE_STICKER_SDK_AUTH_REQUEST";
5909
6708
  const BRIDGE_RESPONSE = "ISSUE_STICKER_SDK_AUTH_RESPONSE";
@@ -6047,10 +6846,16 @@ function AuthProvider({ apiKey, children }) {
6047
6846
  };
6048
6847
  await saveToken(result.accessToken);
6049
6848
  setSdkMode("member");
6050
- const current = stateRef.current;
6051
- const tenantId = ((_a = result.context) == null ? void 0 : _a.tenantId) || current.tenantId;
6052
- const projectId = ((_b2 = result.context) == null ? void 0 : _b2.projectId) || current.projectId;
6053
- const accountId = ((_c2 = result.context) == null ? void 0 : _c2.accountId) || current.accountId;
6849
+ const current2 = stateRef.current;
6850
+ const tenantId = ((_a = result.context) == null ? void 0 : _a.tenantId) || current2.tenantId;
6851
+ const projectId = ((_b2 = result.context) == null ? void 0 : _b2.projectId) || current2.projectId;
6852
+ const accountId = ((_c2 = result.context) == null ? void 0 : _c2.accountId) || current2.accountId;
6853
+ if (projectId && projectId !== current2.projectId) {
6854
+ resetWidgetMaskingPolicy();
6855
+ void refreshMaskingPolicySnapshot({ timeoutMs: MASKING_POLICY_PREFETCH_TIMEOUT_MS }).catch(
6856
+ () => void 0
6857
+ );
6858
+ }
6054
6859
  if (tenantId && projectId) {
6055
6860
  await saveAccountInfo({
6056
6861
  tenantId,
@@ -6164,6 +6969,7 @@ function AuthProvider({ apiKey, children }) {
6164
6969
  const applyPublicContext = useCallback(async () => {
6165
6970
  setSdkMode("public");
6166
6971
  setApiKey(apiKey);
6972
+ resetWidgetMaskingPolicy();
6167
6973
  const result = await authService.publicInit(apiKey);
6168
6974
  const user = getConfiguredUser();
6169
6975
  const nextAuthBridgeUrl = result.authBridgeUrl ?? null;
@@ -6187,6 +6993,9 @@ function AuthProvider({ apiKey, children }) {
6187
6993
  token: null,
6188
6994
  error: null
6189
6995
  }));
6996
+ void refreshMaskingPolicySnapshot({ timeoutMs: MASKING_POLICY_PREFETCH_TIMEOUT_MS }).catch(
6997
+ () => void 0
6998
+ );
6190
6999
  return result;
6191
7000
  }, [apiKey, getConfiguredUser]);
6192
7001
  useEffect(() => {
@@ -6223,14 +7032,14 @@ function AuthProvider({ apiKey, children }) {
6223
7032
  return;
6224
7033
  }
6225
7034
  if (message.type === BRIDGE_RESPONSE && message.requestId) {
6226
- const pending = pendingRequestsRef.current.get(message.requestId);
6227
- if (!pending) return;
6228
- window.clearTimeout(pending.timeoutId);
7035
+ const pending2 = pendingRequestsRef.current.get(message.requestId);
7036
+ if (!pending2) return;
7037
+ window.clearTimeout(pending2.timeoutId);
6229
7038
  pendingRequestsRef.current.delete(message.requestId);
6230
7039
  if (message.success && message.data) {
6231
- pending.resolve(message.data);
7040
+ pending2.resolve(message.data);
6232
7041
  } else {
6233
- pending.reject(new Error(((_b2 = message.error) == null ? void 0 : _b2.message) || ((_c2 = message.error) == null ? void 0 : _c2.code) || "Auth failed"));
7042
+ pending2.reject(new Error(((_b2 = message.error) == null ? void 0 : _b2.message) || ((_c2 = message.error) == null ? void 0 : _c2.code) || "Auth failed"));
6234
7043
  }
6235
7044
  return;
6236
7045
  }
@@ -7393,20 +8202,20 @@ function spring(optionsOrVisualDuration = springDefaults.visualDuration, bounce
7393
8202
  const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
7394
8203
  const sin = Math.sin(angularFreq * t);
7395
8204
  const cos = Math.cos(angularFreq * t);
7396
- const current2 = target - envelope * (A * sin + initialDelta * cos);
8205
+ const current3 = target - envelope * (A * sin + initialDelta * cos);
7397
8206
  const currentVelocity = /* @__PURE__ */ secondsToMilliseconds(envelope * (sinCoeff * sin + cosCoeff * cos));
7398
- state.done = Math.abs(currentVelocity) <= restSpeed && Math.abs(target - current2) <= restDelta;
7399
- state.value = state.done ? target : current2;
8207
+ state.done = Math.abs(currentVelocity) <= restSpeed && Math.abs(target - current3) <= restDelta;
8208
+ state.value = state.done ? target : current3;
7400
8209
  return state;
7401
8210
  }
7402
- const current = resolveSpring(t);
8211
+ const current2 = resolveSpring(t);
7403
8212
  if (!isResolvedFromDuration) {
7404
8213
  const currentVelocity = /* @__PURE__ */ secondsToMilliseconds(resolveVelocity(t));
7405
- state.done = Math.abs(currentVelocity) <= restSpeed && Math.abs(target - current) <= restDelta;
8214
+ state.done = Math.abs(currentVelocity) <= restSpeed && Math.abs(target - current2) <= restDelta;
7406
8215
  } else {
7407
8216
  state.done = t >= duration;
7408
8217
  }
7409
- state.value = state.done ? target : current;
8218
+ state.value = state.done ? target : current2;
7410
8219
  return state;
7411
8220
  },
7412
8221
  toString: () => {
@@ -7427,9 +8236,9 @@ spring.applyToOptions = (options) => {
7427
8236
  return options;
7428
8237
  };
7429
8238
  const velocitySampleDuration = 5;
7430
- function getGeneratorVelocity(resolveValue, t, current) {
8239
+ function getGeneratorVelocity(resolveValue, t, current2) {
7431
8240
  const prevT = Math.max(t - velocitySampleDuration, 0);
7432
- return /* @__PURE__ */ velocityPerSecond(current - resolveValue(prevT), t - prevT);
8241
+ return /* @__PURE__ */ velocityPerSecond(current2 - resolveValue(prevT), t - prevT);
7433
8242
  }
7434
8243
  function inertia({ keyframes: keyframes2, velocity = 0, power = 0.8, timeConstant = 325, bounceDamping = 10, bounceStiffness = 500, modifyTarget, min, max, restDelta = 0.5, restSpeed }) {
7435
8244
  const origin = keyframes2[0];
@@ -7806,8 +8615,8 @@ class JSAnimation extends WithPromise {
7806
8615
  if (this.generator.velocity) {
7807
8616
  return this.generator.velocity(t);
7808
8617
  }
7809
- const current = this.generator.next(t).value;
7810
- return getGeneratorVelocity((s) => this.generator.next(s).value, t, current);
8618
+ const current2 = this.generator.next(t).value;
8619
+ return getGeneratorVelocity((s) => this.generator.next(s).value, t, current2);
7811
8620
  }
7812
8621
  get speed() {
7813
8622
  return this.playbackSpeed;
@@ -8436,11 +9245,11 @@ class NativeAnimationExtended extends NativeAnimation {
8436
9245
  });
8437
9246
  const sampleTime = Math.max(sampleDelta, time.now() - this.startTime);
8438
9247
  const delta = clamp$2(0, sampleDelta, sampleTime - sampleDelta);
8439
- const current = sampleAnimation.sample(sampleTime).value;
9248
+ const current2 = sampleAnimation.sample(sampleTime).value;
8440
9249
  const { name: name2 } = this.options;
8441
9250
  if (element2 && name2)
8442
- setStyle(element2, name2, current);
8443
- motionValue2.setWithVelocity(sampleAnimation.sample(Math.max(0, sampleTime - delta)).value, current, delta);
9251
+ setStyle(element2, name2, current2);
9252
+ motionValue2.setWithVelocity(sampleAnimation.sample(Math.max(0, sampleTime - delta)).value, current2, delta);
8444
9253
  sampleAnimation.stop();
8445
9254
  }
8446
9255
  }
@@ -8457,11 +9266,11 @@ const isAnimatable = (value, name2) => {
8457
9266
  return false;
8458
9267
  };
8459
9268
  function hasKeyframesChanged(keyframes2) {
8460
- const current = keyframes2[0];
9269
+ const current2 = keyframes2[0];
8461
9270
  if (keyframes2.length === 1)
8462
9271
  return true;
8463
9272
  for (let i = 0; i < keyframes2.length; i++) {
8464
- if (keyframes2[i] !== current)
9273
+ if (keyframes2[i] !== current2)
8465
9274
  return true;
8466
9275
  }
8467
9276
  }
@@ -8720,10 +9529,10 @@ class MotionValue {
8720
9529
  this.setCurrent(init);
8721
9530
  this.owner = options.owner;
8722
9531
  }
8723
- setCurrent(current) {
8724
- this.current = current;
9532
+ setCurrent(current2) {
9533
+ this.current = current2;
8725
9534
  this.updatedAt = time.now();
8726
- if (this.canTrackVelocity === null && current !== void 0) {
9535
+ if (this.canTrackVelocity === null && current2 !== void 0) {
8727
9536
  this.canTrackVelocity = isFloat(this.current);
8728
9537
  }
8729
9538
  }
@@ -8825,8 +9634,8 @@ class MotionValue {
8825
9634
  this.passiveEffect(v, this.updateAndNotify);
8826
9635
  }
8827
9636
  }
8828
- setWithVelocity(prev, current, delta) {
8829
- this.set(current);
9637
+ setWithVelocity(prev, current2, delta) {
9638
+ this.set(current2);
8830
9639
  this.prev = void 0;
8831
9640
  this.prevFrameValue = prev;
8832
9641
  this.prevUpdatedAt = this.updatedAt - delta;
@@ -9084,17 +9893,17 @@ const splitCSSVariableRegex = (
9084
9893
  // eslint-disable-next-line redos-detector/no-unsafe-regex -- false positive, as it can match a lot of words
9085
9894
  /^var\(--(?:([\w-]+)|([\w-]+), ?([a-zA-Z\d ()%#.,-]+))\)/u
9086
9895
  );
9087
- function parseCSSVariable(current) {
9088
- const match = splitCSSVariableRegex.exec(current);
9896
+ function parseCSSVariable(current2) {
9897
+ const match = splitCSSVariableRegex.exec(current2);
9089
9898
  if (!match)
9090
9899
  return [,];
9091
9900
  const [, token1, token2, fallback] = match;
9092
9901
  return [`--${token1 ?? token2}`, fallback];
9093
9902
  }
9094
9903
  const maxDepth = 4;
9095
- function getVariableValue(current, element2, depth = 1) {
9096
- invariant(depth <= maxDepth, `Max CSS variable fallback depth detected in property "${current}". This may indicate a circular fallback dependency.`, "max-css-var-depth");
9097
- const [token, fallback] = parseCSSVariable(current);
9904
+ function getVariableValue(current2, element2, depth = 1) {
9905
+ invariant(depth <= maxDepth, `Max CSS variable fallback depth detected in property "${current2}". This may indicate a circular fallback dependency.`, "max-css-var-depth");
9906
+ const [token, fallback] = parseCSSVariable(current2);
9098
9907
  if (!token)
9099
9908
  return;
9100
9909
  const resolved = window.getComputedStyle(element2).getPropertyValue(token);
@@ -9114,15 +9923,15 @@ function getValueState(visualElement) {
9114
9923
  }
9115
9924
  function resolveVariantFromProps(props, definition2, custom, visualElement) {
9116
9925
  if (typeof definition2 === "function") {
9117
- const [current, velocity] = getValueState(visualElement);
9118
- definition2 = definition2(custom !== void 0 ? custom : props.custom, current, velocity);
9926
+ const [current2, velocity] = getValueState(visualElement);
9927
+ definition2 = definition2(custom !== void 0 ? custom : props.custom, current2, velocity);
9119
9928
  }
9120
9929
  if (typeof definition2 === "string") {
9121
9930
  definition2 = props.variants && props.variants[definition2];
9122
9931
  }
9123
9932
  if (typeof definition2 === "function") {
9124
- const [current, velocity] = getValueState(visualElement);
9125
- definition2 = definition2(custom !== void 0 ? custom : props.custom, current, velocity);
9933
+ const [current2, velocity] = getValueState(visualElement);
9934
+ definition2 = definition2(custom !== void 0 ? custom : props.custom, current2, velocity);
9126
9935
  }
9127
9936
  return definition2;
9128
9937
  }
@@ -13504,8 +14313,8 @@ const addPointerInfo = (handler) => (event) => isPrimaryPointer(event) && handle
13504
14313
  function addPointerEvent(target, eventName, handler, options) {
13505
14314
  return addDomEvent(target, eventName, addPointerInfo(handler), options);
13506
14315
  }
13507
- const getContextWindow = ({ current }) => {
13508
- return current ? current.ownerDocument.defaultView : null;
14316
+ const getContextWindow = ({ current: current2 }) => {
14317
+ return current2 ? current2.ownerDocument.defaultView : null;
13509
14318
  };
13510
14319
  const distance = (a, b) => Math.abs(a - b);
13511
14320
  function distance2D(a, b) {
@@ -13595,16 +14404,16 @@ class PanSession {
13595
14404
  * Start tracking scroll on ancestors and window.
13596
14405
  */
13597
14406
  startScrollTracking(element2) {
13598
- let current = element2.parentElement;
13599
- while (current) {
13600
- const style = getComputedStyle(current);
14407
+ let current2 = element2.parentElement;
14408
+ while (current2) {
14409
+ const style = getComputedStyle(current2);
13601
14410
  if (overflowStyles.has(style.overflowX) || overflowStyles.has(style.overflowY)) {
13602
- this.scrollPositions.set(current, {
13603
- x: current.scrollLeft,
13604
- y: current.scrollTop
14411
+ this.scrollPositions.set(current2, {
14412
+ x: current2.scrollLeft,
14413
+ y: current2.scrollTop
13605
14414
  });
13606
14415
  }
13607
- current = current.parentElement;
14416
+ current2 = current2.parentElement;
13608
14417
  }
13609
14418
  this.scrollPositions.set(window, {
13610
14419
  x: window.scrollX,
@@ -13632,11 +14441,11 @@ class PanSession {
13632
14441
  if (!initial)
13633
14442
  return;
13634
14443
  const isWindow = target === window;
13635
- const current = isWindow ? { x: window.scrollX, y: window.scrollY } : {
14444
+ const current2 = isWindow ? { x: window.scrollX, y: window.scrollY } : {
13636
14445
  x: target.scrollLeft,
13637
14446
  y: target.scrollTop
13638
14447
  };
13639
- const delta = { x: current.x - initial.x, y: current.y - initial.y };
14448
+ const delta = { x: current2.x - initial.x, y: current2.y - initial.y };
13640
14449
  if (delta.x === 0 && delta.y === 0)
13641
14450
  return;
13642
14451
  if (isWindow) {
@@ -13650,7 +14459,7 @@ class PanSession {
13650
14459
  this.history[0].y -= delta.y;
13651
14460
  }
13652
14461
  }
13653
- this.scrollPositions.set(target, current);
14462
+ this.scrollPositions.set(target, current2);
13654
14463
  frame.update(this.updatePoint, true);
13655
14464
  }
13656
14465
  updateHandlers(handlers2) {
@@ -13838,18 +14647,18 @@ class VisualElementDragControls {
13838
14647
  this.visualElement.projection.target = void 0;
13839
14648
  }
13840
14649
  eachAxis((axis) => {
13841
- let current = this.getAxisMotionValue(axis).get() || 0;
13842
- if (percent.test(current)) {
14650
+ let current2 = this.getAxisMotionValue(axis).get() || 0;
14651
+ if (percent.test(current2)) {
13843
14652
  const { projection } = this.visualElement;
13844
14653
  if (projection && projection.layout) {
13845
14654
  const measuredAxis = projection.layout.layoutBox[axis];
13846
14655
  if (measuredAxis) {
13847
14656
  const length = calcLength(measuredAxis);
13848
- current = length * (parseFloat(current) / 100);
14657
+ current2 = length * (parseFloat(current2) / 100);
13849
14658
  }
13850
14659
  }
13851
14660
  }
13852
- this.originPoint[axis] = current;
14661
+ this.originPoint[axis] = current2;
13853
14662
  });
13854
14663
  if (onDragStart) {
13855
14664
  frame.update(() => onDragStart(event, info), false, true);
@@ -14067,8 +14876,8 @@ class VisualElementDragControls {
14067
14876
  const axisValue = this.getAxisMotionValue(axis);
14068
14877
  if (projection && projection.layout) {
14069
14878
  const { min, max } = projection.layout.layoutBox[axis];
14070
- const current = axisValue.get() || 0;
14071
- axisValue.set(point2[axis] - mixNumber$1(min, max, 0.5) + current);
14879
+ const current2 = axisValue.get() || 0;
14880
+ axisValue.set(point2[axis] - mixNumber$1(min, max, 0.5) + current2);
14072
14881
  }
14073
14882
  });
14074
14883
  }
@@ -14399,10 +15208,10 @@ function handleHoverEvent(node2, event, lifecycle) {
14399
15208
  }
14400
15209
  class HoverGesture extends Feature {
14401
15210
  mount() {
14402
- const { current } = this.node;
14403
- if (!current)
15211
+ const { current: current2 } = this.node;
15212
+ if (!current2)
14404
15213
  return;
14405
- this.unmount = hover(current, (_element, startEvent) => {
15214
+ this.unmount = hover(current2, (_element, startEvent) => {
14406
15215
  handleHoverEvent(this.node, startEvent, "Start");
14407
15216
  return (endEvent) => handleHoverEvent(this.node, endEvent, "End");
14408
15217
  });
@@ -14455,11 +15264,11 @@ function handlePressEvent(node2, event, lifecycle) {
14455
15264
  }
14456
15265
  class PressGesture extends Feature {
14457
15266
  mount() {
14458
- const { current } = this.node;
14459
- if (!current)
15267
+ const { current: current2 } = this.node;
15268
+ if (!current2)
14460
15269
  return;
14461
15270
  const { globalTapTarget, propagate } = this.node.props;
14462
- this.unmount = press(current, (_element, startEvent) => {
15271
+ this.unmount = press(current2, (_element, startEvent) => {
14463
15272
  handlePressEvent(this.node, startEvent, "Start");
14464
15273
  return (endEvent, { success }) => handlePressEvent(this.node, endEvent, success ? "End" : "Cancel");
14465
15274
  }, {
@@ -14657,7 +15466,10 @@ const ToastItem = forwardRef(function ToastItem2({ t, behindCount, onDismiss },
14657
15466
  ),
14658
15467
  children: /* @__PURE__ */ jsxs("div", { className: cn$1("flex items-center gap-2.5 px-3 py-2.5", style.tint), children: [
14659
15468
  t.type !== "default" ? /* @__PURE__ */ jsx(Icon2, { className: cn$1("h-4 w-4 shrink-0", style.iconColor) }) : null,
14660
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 text-sm font-medium text-text-primary", children: t.message }),
15469
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1 text-sm text-text-primary", children: [
15470
+ t.title ? /* @__PURE__ */ jsx("span", { className: "block font-semibold", children: t.title }) : null,
15471
+ /* @__PURE__ */ jsx("span", { className: t.title ? "block" : "font-medium", children: t.message })
15472
+ ] }),
14661
15473
  t.action ? /* @__PURE__ */ jsx(
14662
15474
  "button",
14663
15475
  {
@@ -14765,235 +15577,6 @@ function toIssueFilterParams(filters) {
14765
15577
  ...countRecord(filters.notionFieldValues) ? { notionFieldValues: filters.notionFieldValues } : {}
14766
15578
  };
14767
15579
  }
14768
- async function uploadSignedMedia(file, mediaType, issueId) {
14769
- return uploadSignedMediaWithApi(
14770
- {
14771
- post: (endpoint, body) => apiClient.post(endpoint, body),
14772
- directUpload
14773
- },
14774
- file,
14775
- mediaType,
14776
- { issueId }
14777
- );
14778
- }
14779
- function issuesEndpoint() {
14780
- return "/api/sdk/issues";
14781
- }
14782
- function guestOwnerQuery() {
14783
- var _a;
14784
- const mode = getSdkMode();
14785
- if (mode !== "public") return "";
14786
- return buildGuestOwnerQuery(mode, (_a = SDKManager.getInstance().config.user) == null ? void 0 : _a.id);
14787
- }
14788
- let issuesCache = null;
14789
- const CACHE_DURATION_MS = 30 * 1e3;
14790
- const STICKER_PAGE_SIZE = 500;
14791
- const STICKER_FETCH_LIMIT = 2e3;
14792
- function getFilterKey(filters) {
14793
- var _a;
14794
- const normalizeArray = (value) => {
14795
- if (value === "all" || !value) return "all";
14796
- if (Array.isArray(value)) {
14797
- return value.length === 0 ? "all" : [...value].sort().join(",");
14798
- }
14799
- return String(value);
14800
- };
14801
- const normalizeRecord = (record) => {
14802
- if (!record) return "";
14803
- const entries = Object.entries(record).map(([k, v]) => [k, [...v].sort()]).sort(([a], [b]) => a.localeCompare(b));
14804
- return JSON.stringify(entries);
14805
- };
14806
- return JSON.stringify({
14807
- sdkMode: getSdkMode() || "public",
14808
- url: normalizeArray(filters.url),
14809
- projectId: filters.projectId || "all",
14810
- externalUserId: filters.externalUserId || "",
14811
- search: ((_a = filters.search) == null ? void 0 : _a.trim()) || "",
14812
- dateFrom: filters.dateFrom || "",
14813
- dateTo: filters.dateTo || "",
14814
- page: filters.page ?? 0,
14815
- pageSize: filters.pageSize ?? 0,
14816
- custom: normalizeRecord(filters.customFieldValues),
14817
- jira: normalizeRecord(filters.jiraFieldValues),
14818
- notion: normalizeRecord(filters.notionFieldValues),
14819
- sortOrder: filters.sortOrder || ""
14820
- });
14821
- }
14822
- function invalidateIssuesCache() {
14823
- issuesCache = null;
14824
- }
14825
- function recordToFilterArray(record, keyFieldName) {
14826
- if (!record) return null;
14827
- const entries = Object.entries(record).filter(([, values]) => values.length > 0);
14828
- if (entries.length === 0) return null;
14829
- return entries.map(
14830
- ([key, values]) => ({ [keyFieldName]: key, values })
14831
- );
14832
- }
14833
- function buildFilterQueryParams(filters) {
14834
- var _a;
14835
- const params = new URLSearchParams();
14836
- if (filters.projectId) {
14837
- params.set("project_id", filters.projectId);
14838
- }
14839
- if (filters.url && filters.url !== "all") {
14840
- if (Array.isArray(filters.url) && filters.url.length > 0) {
14841
- params.set("url", filters.url.join(","));
14842
- } else if (typeof filters.url === "string") {
14843
- params.set("url", filters.url);
14844
- }
14845
- }
14846
- const search2 = (_a = filters.search) == null ? void 0 : _a.trim();
14847
- if (search2) {
14848
- params.set("search", search2);
14849
- }
14850
- if (filters.dateFrom) {
14851
- params.set("date_from", filters.dateFrom);
14852
- }
14853
- if (filters.dateTo) {
14854
- params.set("date_to", filters.dateTo);
14855
- }
14856
- const customArr = recordToFilterArray(filters.customFieldValues, "fieldDefinitionId");
14857
- if (customArr) {
14858
- params.set("custom_field_filters", JSON.stringify(customArr));
14859
- }
14860
- const jiraArr = recordToFilterArray(filters.jiraFieldValues, "fieldName");
14861
- if (jiraArr) {
14862
- params.set("jira_field_filters", JSON.stringify(jiraArr));
14863
- }
14864
- const notionArr = recordToFilterArray(filters.notionFieldValues, "fieldName");
14865
- if (notionArr) {
14866
- params.set("notion_field_filters", JSON.stringify(notionArr));
14867
- }
14868
- if (filters.externalUserId) {
14869
- params.set("external_user_id", filters.externalUserId);
14870
- }
14871
- if (typeof filters.page === "number" && filters.page > 0) {
14872
- params.set("page", String(filters.page));
14873
- }
14874
- if (typeof filters.pageSize === "number" && filters.pageSize > 0) {
14875
- params.set("page_size", String(filters.pageSize));
14876
- }
14877
- if (filters.sortOrder) {
14878
- params.set("sort_order", filters.sortOrder);
14879
- }
14880
- const queryString = params.toString();
14881
- return queryString ? `?${queryString}` : "";
14882
- }
14883
- const issueService = {
14884
- async createIssue(issueData) {
14885
- const sanitizedData = sanitizeString(issueData);
14886
- const screenshotUploadIds = (sanitizedData.screenshot || []).map((url) => getRegisteredUploadId(url, "screenshot")).filter((id2) => typeof id2 === "string" && id2.length > 0);
14887
- const videoUploadId = sanitizedData.video ? getRegisteredUploadId(sanitizedData.video, "video") : void 0;
14888
- if (screenshotUploadIds.length > 0) {
14889
- sanitizedData.screenshot_upload_ids = screenshotUploadIds;
14890
- }
14891
- if (videoUploadId) {
14892
- sanitizedData.video_upload_id = videoUploadId;
14893
- }
14894
- const issue = await apiClient.post(issuesEndpoint(), sanitizedData);
14895
- invalidateIssuesCache();
14896
- return issue;
14897
- },
14898
- async getIssues(filters = {}, options = {}) {
14899
- if (!filters.projectId) {
14900
- throw new Error("프로젝트 ID가 필요합니다. 로그인 상태를 확인해주세요.");
14901
- }
14902
- const filterKey = getFilterKey(filters);
14903
- const now2 = Date.now();
14904
- if (!options.forceRefresh && issuesCache && issuesCache.filterKey === filterKey && now2 - issuesCache.timestamp < CACHE_DURATION_MS) {
14905
- return issuesCache.data;
14906
- }
14907
- const queryParams = buildFilterQueryParams(filters);
14908
- const { items: issues = [] } = await apiClient.get(
14909
- `${issuesEndpoint()}${queryParams}`
14910
- );
14911
- issuesCache = {
14912
- data: issues,
14913
- timestamp: now2,
14914
- filterKey
14915
- };
14916
- return issues;
14917
- },
14918
- async getIssueStickers(filters = {}, options = {}) {
14919
- var _a, _b2;
14920
- if (!filters.projectId) {
14921
- throw new Error("프로젝트 ID가 필요합니다. 로그인 상태를 확인해주세요.");
14922
- }
14923
- const filterKey = `${getFilterKey(filters)}:stickers`;
14924
- const now2 = Date.now();
14925
- if (!options.forceRefresh && issuesCache && issuesCache.filterKey === filterKey && now2 - issuesCache.timestamp < CACHE_DURATION_MS) {
14926
- return issuesCache.data;
14927
- }
14928
- const issues = [];
14929
- let page = 1;
14930
- let hasMore = true;
14931
- while (hasMore && issues.length <= STICKER_FETCH_LIMIT) {
14932
- const response = await apiClient.getResponse(
14933
- `${issuesEndpoint()}/stickers${buildFilterQueryParams({
14934
- ...filters,
14935
- page,
14936
- pageSize: STICKER_PAGE_SIZE
14937
- })}`
14938
- );
14939
- const pageItems = ((_a = response.data) == null ? void 0 : _a.items) ?? [];
14940
- issues.push(...pageItems);
14941
- hasMore = Boolean((_b2 = response.data) == null ? void 0 : _b2.hasMore) && pageItems.length > 0;
14942
- page += 1;
14943
- }
14944
- issuesCache = {
14945
- data: issues,
14946
- timestamp: now2,
14947
- filterKey
14948
- };
14949
- return issues;
14950
- },
14951
- invalidateCache() {
14952
- invalidateIssuesCache();
14953
- },
14954
- // 계약 기준: 백엔드 GET /api/sdk/issues/:id 는 back#101부터 읽기-시점-스크럽된
14955
- // llm_prompt 를 상세 응답에 포함한다(목록 응답에는 없음) — 스코프를 이 반환 타입에만 한정한다.
14956
- async getIssue(id2) {
14957
- return apiClient.get(
14958
- `${issuesEndpoint()}/${id2}${guestOwnerQuery()}`
14959
- );
14960
- },
14961
- async updateIssue(id2, updates) {
14962
- const sanitizedUpdates = sanitizeString(updates);
14963
- const issue = await apiClient.put(
14964
- `${issuesEndpoint()}/${id2}${guestOwnerQuery()}`,
14965
- sanitizedUpdates
14966
- );
14967
- invalidateIssuesCache();
14968
- return issue;
14969
- },
14970
- /**
14971
- * 재생성. 스펙상 세 채널 모두에서 되고 역할과 무관하다 — 검사는 역할이 아니라
14972
- * 그 이슈에 접근할 수 있는지다(백엔드가 판정한다).
14973
- *
14974
- * 응답으로 갱신된 이슈가 오지만 상태는 다시 pending 이다. 호출부는 응답을
14975
- * 그대로 쓰지 말고 폴링으로 이어받아야 한다.
14976
- */
14977
- async regenerateIssue(id2) {
14978
- const issue = await apiClient.post(
14979
- `${issuesEndpoint()}/${encodeURIComponent(id2)}/regenerate${guestOwnerQuery()}`,
14980
- {}
14981
- );
14982
- invalidateIssuesCache();
14983
- return issue;
14984
- },
14985
- async deleteIssue(id2, options) {
14986
- const endpoint = `${issuesEndpoint()}/${id2}${guestOwnerQuery()}`;
14987
- if (options == null ? void 0 : options.deleteExternal) {
14988
- await apiClient.delete(endpoint, {
14989
- body: { deleteExternal: true }
14990
- });
14991
- } else {
14992
- await apiClient.delete(endpoint);
14993
- }
14994
- invalidateIssuesCache();
14995
- }
14996
- };
14997
15580
  async function loadIssuesFromServer(url, projectId, externalUserId, filters, options = {}) {
14998
15581
  if (!projectId) return [];
14999
15582
  try {
@@ -15171,18 +15754,18 @@ function UnrenderedStickerDialog({
15171
15754
  {
15172
15755
  role: "dialog",
15173
15756
  "aria-modal": "false",
15174
- "aria-label": "미표시 스티커 목록",
15757
+ "aria-label": "표시하지 못한 스티커 목록",
15175
15758
  className: "pointer-events-auto flex h-[min(480px,calc(100vh-64px))] w-[min(560px,calc(100vw-32px))] flex-col overflow-hidden rounded-xl border border-slate-200 bg-white text-slate-900 shadow-2xl",
15176
15759
  children: [
15177
15760
  /* @__PURE__ */ jsxs("header", { className: "flex items-center justify-between border-b border-slate-100 px-5 py-3.5", children: [
15178
- /* @__PURE__ */ jsx("h2", { className: "min-w-0 truncate text-sm font-semibold", children: "미표시 스티커" }),
15761
+ /* @__PURE__ */ jsx("h2", { className: "min-w-0 truncate text-sm font-semibold", children: "표시하지 못한 스티커" }),
15179
15762
  /* @__PURE__ */ jsx(
15180
15763
  "button",
15181
15764
  {
15182
15765
  type: "button",
15183
15766
  className: "inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-slate-500 hover:bg-slate-100 hover:text-slate-900",
15184
15767
  onClick: () => onOpenChange(false),
15185
- "aria-label": "미표시 스티커 목록 닫기",
15768
+ "aria-label": "표시하지 못한 스티커 목록 닫기",
15186
15769
  children: /* @__PURE__ */ jsx(X$1, { className: "h-4 w-4" })
15187
15770
  }
15188
15771
  )
@@ -15360,7 +15943,8 @@ function OverlayShell({
15360
15943
  }
15361
15944
  const ISSUE_DRAFT_DISABLED_MESSAGE = "지금은 AI 작성을 사용할 수 없습니다. 제목과 설명을 직접 작성해주세요.";
15362
15945
  const LABEL = "기대한 동작";
15363
- const PLACEHOLDER = "예: 할인 쿠폰을 적용하면 결제 금액에서 즉시 차감되어야 합니다";
15946
+ const ISSUE_INTENT_PLACEHOLDER = "기대한 동작을 적으면 AI가 제목과 설명을 작성합니다.\n예: 쿠폰을 적용하면 결제 금액이 바로 줄어야 합니다";
15947
+ const PLACEHOLDER = ISSUE_INTENT_PLACEHOLDER;
15364
15948
  const SWITCH_TO_MANUAL = "직접 작성";
15365
15949
  const SWITCH_TO_MANUAL_TOOLTIP = "제목, 설명 등 이슈 필드를 직접 작성합니다";
15366
15950
  const SUBMIT = "보내기";
@@ -15397,20 +15981,28 @@ function IssueIntentBubble({
15397
15981
  if (isFocused(node2) || Date.now() >= deadline) return;
15398
15982
  frame2 = requestAnimationFrame(attempt);
15399
15983
  };
15400
- window.addEventListener("pointerdown", abandon, { capture: true });
15984
+ const pointerScopes = /* @__PURE__ */ new Set([window, node2.getRootNode()]);
15985
+ pointerScopes.forEach(
15986
+ (scope) => scope.addEventListener("pointerdown", abandon, { capture: true })
15987
+ );
15401
15988
  window.addEventListener("keydown", abandon, { capture: true });
15402
15989
  attempt();
15403
15990
  return () => {
15404
- window.removeEventListener("pointerdown", abandon, { capture: true });
15991
+ pointerScopes.forEach(
15992
+ (scope) => scope.removeEventListener("pointerdown", abandon, { capture: true })
15993
+ );
15405
15994
  window.removeEventListener("keydown", abandon, { capture: true });
15406
15995
  if (frame2) cancelAnimationFrame(frame2);
15407
15996
  };
15408
15997
  }, []);
15998
+ const minHeightRef = useRef(null);
15409
15999
  useLayoutEffect(() => {
15410
16000
  const node2 = inputRef.current;
15411
16001
  if (!node2) return;
15412
16002
  node2.style.height = "auto";
15413
- node2.style.height = `${Math.min(node2.scrollHeight, MAX_INPUT_HEIGHT)}px`;
16003
+ minHeightRef.current ?? (minHeightRef.current = node2.scrollHeight);
16004
+ const next = Math.max(node2.scrollHeight, minHeightRef.current);
16005
+ node2.style.height = `${Math.min(next, MAX_INPUT_HEIGHT)}px`;
15414
16006
  }, [value]);
15415
16007
  return (
15416
16008
  /*
@@ -15426,7 +16018,7 @@ function IssueIntentBubble({
15426
16018
  /* @__PURE__ */ jsxs(
15427
16019
  "div",
15428
16020
  {
15429
- className: `${ISSUE_STICKER_REPORT_FORM_CLASS} w-[min(20rem,calc(100vw_-_1.5rem))] space-y-1.5 rounded-md border border-line-subtle bg-surface-1 p-1.5 transition-colors duration-fast ease-out focus-within:border-line-focus`,
16021
+ className: `${ISSUE_STICKER_REPORT_FORM_CLASS} w-[min(24rem,calc(100vw_-_1.5rem))] space-y-1.5 rounded-md border border-line-subtle bg-surface-1 p-1.5 transition-colors duration-fast ease-out focus-within:border-line-focus`,
15430
16022
  children: [
15431
16023
  /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-1.5", children: [
15432
16024
  /* @__PURE__ */ jsx(
@@ -15736,7 +16328,9 @@ DialogClose$1.displayName = CLOSE_NAME;
15736
16328
  function getState$2(open) {
15737
16329
  return open ? "open" : "closed";
15738
16330
  }
15739
- const Dialog = Dialog$1;
16331
+ const [Dialog, useDialogContentDismissRef] = createShadowDismissRoot(Dialog$1, {
16332
+ focusOutside: false
16333
+ });
15740
16334
  const DialogClose = DialogClose$1;
15741
16335
  const DialogPortal = ({ children }) => {
15742
16336
  const portalContainer = usePortalContainer();
@@ -15769,13 +16363,13 @@ const DialogContent = React.forwardRef(
15769
16363
  ...props
15770
16364
  }, ref) => {
15771
16365
  const [content2, setContent] = React.useState(null);
16366
+ const dismissRef = useDialogContentDismissRef(ref);
15772
16367
  const composedRef = React.useCallback(
15773
16368
  (node2) => {
15774
16369
  setContent(node2);
15775
- if (typeof ref === "function") ref(node2);
15776
- else if (ref) ref.current = node2;
16370
+ dismissRef(node2);
15777
16371
  },
15778
- [ref]
16372
+ [dismissRef]
15779
16373
  );
15780
16374
  useOverlayFocus({ enabled: true, container: content2, initialFocusRef });
15781
16375
  return /* @__PURE__ */ jsxs(DialogPortal, { children: [
@@ -16048,7 +16642,11 @@ function appendSnippet(base, body) {
16048
16642
  ${trimmedBody}`;
16049
16643
  }
16050
16644
  const TRIGGER_LABEL = "고정 문구 선택";
16645
+ const TRIGGER_HINT = "이슈 설명 끝에 추가할 고정 문구를 선택합니다";
16051
16646
  const CLEAR_OPTION_LABEL = "선택 안 함";
16647
+ const EMPTY_TITLE = "등록된 고정 문구가 없습니다";
16648
+ const EMPTY_DESCRIPTION = "테스트 계정처럼 이슈마다 반복해서 적는 내용을 등록해두면 이슈 설명 끝에 추가됩니다.";
16649
+ const DEFAULT_EMPTY_HINT = "어드민 프로젝트 상세의 고정 문구 탭에서 등록합니다.";
16052
16650
  const GROUP_LABEL = {
16053
16651
  project: "공용 문구",
16054
16652
  personal: "내 문구"
@@ -16057,7 +16655,8 @@ function SnippetPicker({
16057
16655
  snippets,
16058
16656
  selectedId,
16059
16657
  onSelect,
16060
- disabled = false
16658
+ disabled = false,
16659
+ emptyHint = DEFAULT_EMPTY_HINT
16061
16660
  }) {
16062
16661
  const [open, setOpen] = useState(false);
16063
16662
  const listRef = useRef(null);
@@ -16116,7 +16715,8 @@ function SnippetPicker({
16116
16715
  "button",
16117
16716
  {
16118
16717
  type: "button",
16119
- disabled: disabled || isEmpty,
16718
+ title: TRIGGER_HINT,
16719
+ disabled,
16120
16720
  className: cn$1(
16121
16721
  "inline-flex max-w-[10rem] items-center gap-1 rounded-sm border border-transparent px-2 py-1 text-xs text-text-secondary outline-none",
16122
16722
  "hover:bg-surface-2 focus-visible:border-line-focus disabled:cursor-not-allowed disabled:opacity-40"
@@ -16134,8 +16734,18 @@ function SnippetPicker({
16134
16734
  align: "start",
16135
16735
  sideOffset: 4,
16136
16736
  onEscapeKeyDown: stopEscapePropagation(),
16137
- className: cn$1(`panel-shell-subtle w-72 p-1.5 shadow-lg ${OVERLAY_LAYER.floating}`),
16138
- children: /* @__PURE__ */ jsxs(
16737
+ className: cn$1(
16738
+ `panel-shell-subtle p-1.5 shadow-lg ${OVERLAY_LAYER.floating}`,
16739
+ isEmpty ? "w-[min(30rem,calc(100vw_-_1.5rem))]" : "w-72"
16740
+ ),
16741
+ children: isEmpty ? (
16742
+ // 비어 있다고 버튼을 막으면 무엇을 하는 버튼인지조차 알 수 없다. 열어서 이유를 알려준다.
16743
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1 break-keep px-2.5 py-2 [overflow-wrap:anywhere]", children: [
16744
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-text-primary", children: EMPTY_TITLE }),
16745
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: EMPTY_DESCRIPTION }),
16746
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: emptyHint })
16747
+ ] })
16748
+ ) : /* @__PURE__ */ jsxs(
16139
16749
  "div",
16140
16750
  {
16141
16751
  ref: listRef,
@@ -16365,7 +16975,7 @@ function useJiraConnectNudge(projectId, enabled) {
16365
16975
  const queryClient2 = useQueryClient();
16366
16976
  const isEnabled = enabled && Boolean(projectId);
16367
16977
  const {
16368
- data: pending,
16978
+ data: pending2,
16369
16979
  refetch,
16370
16980
  isFetched
16371
16981
  } = useQuery({
@@ -16389,7 +16999,7 @@ function useJiraConnectNudge(projectId, enabled) {
16389
16999
  }
16390
17000
  }, [projectId, trackAttempt]);
16391
17001
  const needsConnect = Boolean(
16392
- projectId && (pending == null ? void 0 : pending.some((project) => project.projectId === projectId))
17002
+ projectId && (pending2 == null ? void 0 : pending2.some((project) => project.projectId === projectId))
16393
17003
  );
16394
17004
  const isResolving = isEnabled && !isFetched;
16395
17005
  return { needsConnect, connect, isResolving };
@@ -16520,6 +17130,43 @@ function useAuthInfo() {
16520
17130
  getAuthInfo
16521
17131
  };
16522
17132
  }
17133
+ async function uploadScreenshot(screenshotDataUrl, issueId, options) {
17134
+ const [firstUrl] = await uploadScreenshots([screenshotDataUrl], issueId, options);
17135
+ if (typeof firstUrl === "string" && firstUrl.length > 0) {
17136
+ return firstUrl;
17137
+ }
17138
+ throw new Error("스크린샷 업로드 응답에 URL이 없습니다.");
17139
+ }
17140
+ async function uploadScreenshots(screenshotDataUrls, issueId, options) {
17141
+ const uploads = await uploadScreenshotDataUrls(
17142
+ screenshotDataUrls,
17143
+ uploadSignedMedia,
17144
+ issueId,
17145
+ options
17146
+ );
17147
+ return uploads.map((upload) => upload == null ? void 0 : upload.url);
17148
+ }
17149
+ function useScreenshotUpload() {
17150
+ const upload = async (screenshotDataUrl, options) => {
17151
+ try {
17152
+ const screenshotUrl = await uploadScreenshot(screenshotDataUrl, void 0, options);
17153
+ return screenshotUrl;
17154
+ } catch {
17155
+ return void 0;
17156
+ }
17157
+ };
17158
+ const uploadMany = async (screenshotDataUrls, options) => {
17159
+ try {
17160
+ return await uploadScreenshots(screenshotDataUrls, void 0, options);
17161
+ } catch {
17162
+ return screenshotDataUrls.map(() => void 0);
17163
+ }
17164
+ };
17165
+ return {
17166
+ upload,
17167
+ uploadMany
17168
+ };
17169
+ }
16523
17170
  async function captureScreenshot(options) {
16524
17171
  try {
16525
17172
  const sdk = SDKManager.getInstance();
@@ -16527,26 +17174,33 @@ async function captureScreenshot(options) {
16527
17174
  return await sdk.platform.screenshot.capture(options);
16528
17175
  }
16529
17176
  } catch (err) {
17177
+ if (err instanceof MaskingCaptureError) throw err;
16530
17178
  devLogger.error("screenshotCapture", "스크린샷 캡처 실패:", err);
16531
17179
  }
16532
17180
  return void 0;
16533
17181
  }
16534
- function toScreenshotList(dataUrls, dataUrl) {
16535
- if (dataUrls && dataUrls.length > 0) {
16536
- return dataUrls;
17182
+ async function captureViewportAndElement(element2, excludeSelectors, maskSelectors) {
17183
+ try {
17184
+ if (SDKManager.getInstance().isInitialized()) {
17185
+ return await captureViewportAndElementShots({ element: element2, excludeSelectors, maskSelectors });
17186
+ }
17187
+ } catch (err) {
17188
+ devLogger.error("screenshotCapture", "스크린샷 캡처 실패:", err);
17189
+ if (err instanceof MaskingCaptureError) return { maskingDropped: true };
16537
17190
  }
16538
- return dataUrl ? [dataUrl] : [];
17191
+ return {};
16539
17192
  }
16540
- async function resolveClickPositionScreenshots(position2) {
16541
- const immediate = toScreenshotList(position2.screenshotDataUrls, position2.screenshotDataUrl);
16542
- if (immediate.length > 0) {
16543
- return immediate;
16544
- }
16545
- if (!position2.screenshotCapture) {
16546
- return [];
16547
- }
16548
- const captured = await position2.screenshotCapture;
16549
- return toScreenshotList(captured.screenshotDataUrls, captured.screenshotDataUrl);
17193
+ function resolveCapturedScreenshots(captured) {
17194
+ return {
17195
+ screenshots: captured.screenshots ?? [],
17196
+ maskingDropped: Boolean(captured.maskingDropped),
17197
+ policyKnown: captured.policyKnown !== false
17198
+ };
17199
+ }
17200
+ async function resolveClickPositionCapture(position2) {
17201
+ return resolveCapturedScreenshots(
17202
+ position2.screenshots && position2.screenshots.length > 0 || !position2.screenshotCapture ? position2 : await position2.screenshotCapture
17203
+ );
16550
17204
  }
16551
17205
  const SCREENSHOT_EXCLUDE_SELECTORS = [
16552
17206
  '[data-issue-sticker-capture-ignore="true"]',
@@ -16582,6 +17236,8 @@ function useClickPosition(isCreatingIssueRef, recordedVideoFile) {
16582
17236
  const [clickPosition, setClickPosition] = useState(null);
16583
17237
  const clickPositionRef = useRef(null);
16584
17238
  const highlightedElementRef = useRef(null);
17239
+ const captureInBackgroundRef = useRef(null);
17240
+ const pendingCaptureRef = useRef(null);
16585
17241
  useEffect(() => {
16586
17242
  clickPositionRef.current = clickPosition;
16587
17243
  }, [clickPosition]);
@@ -16650,23 +17306,35 @@ function useClickPosition(isCreatingIssueRef, recordedVideoFile) {
16650
17306
  }
16651
17307
  }, [recordedVideoFile]);
16652
17308
  useEffect(() => {
16653
- const captureScreenshotsInBackground = async (position2) => {
17309
+ const captureScreenshotsInBackground = async (position2, selectors) => {
16654
17310
  await waitForFormOpen();
16655
- const [fullScreenshotDataUrl, elementScreenshotDataUrl] = await pauseNetworkLogCollectionWhile(
16656
- () => Promise.all([
16657
- captureScreenshot({ excludeSelectors: SCREENSHOT_EXCLUDE_SELECTORS }),
16658
- captureScreenshot({
17311
+ const loaded = selectors ?? await loadCaptureSelectors(() => widgetMaskingPolicySource.getMaskingPolicySnapshot());
17312
+ const maskSelectors = loaded ?? [];
17313
+ const captured = await pauseNetworkLogCollectionWhile(
17314
+ async () => {
17315
+ const viewport = await captureViewportAndElement(
17316
+ position2.element,
17317
+ SCREENSHOT_EXCLUDE_SELECTORS,
17318
+ maskSelectors
17319
+ );
17320
+ let maskingDropped = Boolean(viewport.maskingDropped);
17321
+ const elementShot = viewport.element ?? await captureScreenshot({
16659
17322
  element: position2.element,
16660
- excludeSelectors: SCREENSHOT_EXCLUDE_SELECTORS
16661
- })
16662
- ])
17323
+ excludeSelectors: SCREENSHOT_EXCLUDE_SELECTORS,
17324
+ maskSelectors
17325
+ }).catch((error) => {
17326
+ if (!(error instanceof MaskingCaptureError)) throw error;
17327
+ maskingDropped = true;
17328
+ return void 0;
17329
+ });
17330
+ const screenshots = [viewport.full, elementShot].filter(isCapturedScreenshot);
17331
+ return {
17332
+ screenshots,
17333
+ ...maskingDropped ? { maskingDropped } : {},
17334
+ ...loaded === null ? { policyKnown: false } : {}
17335
+ };
17336
+ }
16663
17337
  );
16664
- const captured = {
16665
- screenshotDataUrl: elementScreenshotDataUrl,
16666
- screenshotDataUrls: [fullScreenshotDataUrl, elementScreenshotDataUrl].filter(
16667
- (dataUrl) => typeof dataUrl === "string" && dataUrl.length > 0
16668
- )
16669
- };
16670
17338
  if (clickPositionRef.current === position2) {
16671
17339
  const merged = { ...position2, ...captured };
16672
17340
  setClickPosition(merged);
@@ -16674,6 +17342,17 @@ function useClickPosition(isCreatingIssueRef, recordedVideoFile) {
16674
17342
  }
16675
17343
  return captured;
16676
17344
  };
17345
+ const startCapture = (position2, selectors) => {
17346
+ const tracked = captureScreenshotsInBackground(
17347
+ position2,
17348
+ selectors
17349
+ ).finally(() => {
17350
+ if (pendingCaptureRef.current === tracked) pendingCaptureRef.current = null;
17351
+ });
17352
+ pendingCaptureRef.current = tracked;
17353
+ return tracked;
17354
+ };
17355
+ captureInBackgroundRef.current = startCapture;
16677
17356
  const handleDocumentClick = (e) => {
16678
17357
  if (!isCreatingIssueRef.current) {
16679
17358
  return;
@@ -16704,7 +17383,7 @@ function useClickPosition(isCreatingIssueRef, recordedVideoFile) {
16704
17383
  videoFile: recordedVideoFile ?? void 0
16705
17384
  };
16706
17385
  if (!newClickPosition.videoFile) {
16707
- newClickPosition.screenshotCapture = captureScreenshotsInBackground(newClickPosition);
17386
+ newClickPosition.screenshotCapture = startCapture(newClickPosition);
16708
17387
  }
16709
17388
  setClickPosition(newClickPosition);
16710
17389
  clickPositionRef.current = newClickPosition;
@@ -16743,85 +17422,95 @@ function useClickPosition(isCreatingIssueRef, recordedVideoFile) {
16743
17422
  setClickPosition(position2);
16744
17423
  clickPositionRef.current = position2;
16745
17424
  };
17425
+ const recaptureScreenshots = (selectors) => {
17426
+ const pending2 = pendingCaptureRef.current;
17427
+ if (pending2) {
17428
+ return pending2.catch(() => void 0).then(() => recaptureScreenshots(selectors));
17429
+ }
17430
+ const current2 = clickPositionRef.current;
17431
+ const capture = captureInBackgroundRef.current;
17432
+ if (!current2 || current2.videoFile || !capture) return Promise.resolve(null);
17433
+ const next = {
17434
+ ...current2,
17435
+ screenshots: void 0,
17436
+ maskingDropped: void 0,
17437
+ policyKnown: void 0
17438
+ };
17439
+ const captured = capture(next, selectors);
17440
+ next.screenshotCapture = captured;
17441
+ setClickPosition(next);
17442
+ clickPositionRef.current = next;
17443
+ return captured;
17444
+ };
16746
17445
  return {
16747
17446
  clickPosition,
16748
17447
  clickPositionRef,
16749
17448
  clearClickPosition,
16750
- setClickPositionManually
17449
+ setClickPositionManually,
17450
+ recaptureScreenshots
16751
17451
  };
16752
17452
  }
16753
- async function uploadScreenshot(screenshotDataUrl, issueId, options) {
16754
- const [firstUrl] = await uploadScreenshots([screenshotDataUrl], issueId, options);
16755
- if (typeof firstUrl === "string" && firstUrl.length > 0) {
16756
- return firstUrl;
16757
- }
16758
- throw new Error("스크린샷 업로드 응답에 URL이 없습니다.");
16759
- }
16760
- async function uploadScreenshots(screenshotDataUrls, issueId, options) {
16761
- const uploads = await uploadScreenshotDataUrls(
16762
- screenshotDataUrls,
16763
- uploadSignedMedia,
16764
- issueId,
16765
- options
16766
- );
16767
- return uploads.map((upload) => upload == null ? void 0 : upload.url);
16768
- }
16769
- function useScreenshotUpload() {
16770
- const upload = async (screenshotDataUrl, options) => {
16771
- try {
16772
- const screenshotUrl = await uploadScreenshot(screenshotDataUrl, void 0, options);
16773
- return screenshotUrl;
16774
- } catch {
16775
- return void 0;
17453
+ async function prepareIssueScreenAttachments(input) {
17454
+ var _a, _b2;
17455
+ const { context } = input;
17456
+ const replayController = ((_a = input.sessionReplay) == null ? void 0 : _a.canAttach) ? input.sessionReplay : void 0;
17457
+ let capture = input.includeScreenshots ? await resolveClickPositionCapture(input.position) : null;
17458
+ await (replayController == null ? void 0 : replayController.whenRestarted());
17459
+ const memoSelectors = (replayController == null ? void 0 : replayController.getMaskMemoSelectors()) ?? null;
17460
+ const memoSets = memoSelectors ? [memoSelectors] : [];
17461
+ const assertCovered = (shots = []) => assertScreenRulesCovered(context.rules, [
17462
+ ...shots.map((shot) => shot.masks.selectors),
17463
+ ...memoSets
17464
+ ]);
17465
+ try {
17466
+ assertCovered(capture == null ? void 0 : capture.screenshots);
17467
+ } catch (error) {
17468
+ if (!(error instanceof MaskingScreenRulesChangedError) || (capture == null ? void 0 : capture.policyKnown) !== false || !input.recaptureScreenshots) {
17469
+ throw error;
16776
17470
  }
16777
- };
16778
- const uploadMany = async (screenshotDataUrls, options) => {
16779
- try {
16780
- return await uploadScreenshots(screenshotDataUrls, void 0, options);
16781
- } catch {
16782
- return screenshotDataUrls.map(() => void 0);
17471
+ assertScreenRulesCovered(context.rules, memoSets);
17472
+ const recaptured = await input.recaptureScreenshots(collectSnapshotSelectors(context.snapshot)).catch(() => null);
17473
+ if (!recaptured) throw error;
17474
+ const shotCount = capture.screenshots.length;
17475
+ capture = resolveCapturedScreenshots(recaptured);
17476
+ if (capture.screenshots.length < shotCount) capture.maskingDropped = true;
17477
+ assertCovered(capture.screenshots);
17478
+ }
17479
+ const replay = replayController ? await replayController.takeSnapshot(((_b2 = context.rules) == null ? void 0 : _b2.screen.mask_selectors) ?? []).catch((error) => {
17480
+ if (isMaskingFailure(error)) throw error;
17481
+ devLogger.error(
17482
+ "useIssueSubmission",
17483
+ "세션 리플레이 스냅샷 실패 (이슈 생성 계속):",
17484
+ error
17485
+ );
17486
+ return null;
17487
+ }) : null;
17488
+ let screenshotUrls = [];
17489
+ let screenshotExcluded = null;
17490
+ if (capture) {
17491
+ const maskedDataUrls = await maskScreenshotsForSubmit(capture.screenshots, context.rules);
17492
+ if (maskedDataUrls.length > 0) {
17493
+ const uploaded = await input.uploadScreenshots(maskedDataUrls);
17494
+ screenshotUrls = uploaded.filter(
17495
+ (url) => typeof url === "string" && url.length > 0
17496
+ );
16783
17497
  }
16784
- };
16785
- return {
16786
- upload,
16787
- uploadMany
16788
- };
16789
- }
16790
- async function uploadVideo(videoFile, issueId) {
16791
- devLogger.log("videoUpload", "영상 파일 업로드 시작", {
16792
- 크기: `${(videoFile.size / 1024 / 1024).toFixed(2)}MB`,
16793
- 타입: videoFile.type
16794
- });
16795
- const result = await uploadSignedMedia(videoFile, "video", issueId);
16796
- devLogger.log("videoUpload", "영상 파일 업로드 성공:", result.url);
16797
- return result.url;
17498
+ if (capture.maskingDropped) screenshotExcluded = screenshotUrls.length > 0 ? "partial" : "all";
17499
+ }
17500
+ return { screenshotUrls, replay, screenshotExcluded };
16798
17501
  }
16799
- function useVideoUpload() {
16800
- const upload = async (videoFile) => uploadVideo(videoFile);
16801
- return {
16802
- upload
16803
- };
17502
+ function restartReplayStartedWithoutPolicy(context, sessionReplay) {
17503
+ var _a;
17504
+ if ((sessionReplay == null ? void 0 : sessionReplay.canAttach) && ((_a = sessionReplay.getMaskMemoSelectors()) == null ? void 0 : _a.length) === 0 && collectSnapshotSelectors(context.snapshot).length > 0) {
17505
+ void sessionReplay.restartRecording();
17506
+ }
16804
17507
  }
16805
- async function uploadSessionReplay(snapshot) {
17508
+ function readSocketConnections() {
16806
17509
  try {
16807
- const file = new File([snapshot.payload], "session-replay.gz", {
16808
- type: "application/gzip"
16809
- });
16810
- const { id: id2 } = await uploadSignedMedia(file, "replay");
16811
- const marker = readReleaseMarker();
16812
- return {
16813
- uploadId: id2,
16814
- meta: {
16815
- window_seconds: snapshot.windowSeconds,
16816
- event_count: snapshot.eventCount,
16817
- size_bytes: snapshot.sizeBytes,
16818
- rrweb_version: snapshot.rrwebVersion,
16819
- privacy_config_version: snapshot.privacyConfigVersion,
16820
- ...marker ? { release: marker.release, release_source: marker.release_source } : {}
16821
- }
16822
- };
16823
- } catch {
16824
- return null;
17510
+ return getCollectorSession().socket.getConnections();
17511
+ } catch (error) {
17512
+ devLogger.error("useIssueSubmission", "소켓 연결 수집 실패 (이슈 생성 계속):", error);
17513
+ return [];
16825
17514
  }
16826
17515
  }
16827
17516
  function assertSubmittable(data) {
@@ -16842,35 +17531,23 @@ function useIssueSubmission({
16842
17531
  notionIntegrationId,
16843
17532
  videoFile,
16844
17533
  sessionReplay,
16845
- snippetId
17534
+ snippetId,
17535
+ onScreenRulesChanged,
17536
+ recaptureScreenshots,
17537
+ videoExclusionNotified
16846
17538
  }) {
16847
17539
  const failedServicesRef = useRef([]);
17540
+ const maskingOutcomeRef = useRef(NO_MASKING_OUTCOME);
16848
17541
  const { fetchNetworkLogs } = useNetworkLogs();
16849
17542
  const { fetchConsoleLogs } = useConsoleLogs();
16850
17543
  const { getAuthInfo } = useAuthInfo();
16851
17544
  const { uploadMany: uploadScreenshots2 } = useScreenshotUpload();
16852
- const { upload: uploadVideo2 } = useVideoUpload();
16853
17545
  const createIssueMutation = useDebouncedMutation({
16854
17546
  mutationFn: async (data) => {
16855
- var _a, _b2, _c2, _d2, _e2, _f2;
17547
+ var _a;
16856
17548
  assertSubmittable(data);
16857
- let screenshotUrls;
16858
- let videoUrl;
16859
- if (data.videoFile) {
16860
- videoUrl = await uploadVideo2(data.videoFile);
16861
- } else if (data.position.videoFile) {
16862
- videoUrl = await uploadVideo2(data.position.videoFile);
16863
- } else {
16864
- const screenshotDataUrls = await resolveClickPositionScreenshots(data.position);
16865
- if (screenshotDataUrls.length > 0) {
16866
- const uploadedUrls = await uploadScreenshots2(screenshotDataUrls, {
16867
- useWebWorker: ((_a = SDKManager.getInstance().config.screenshotCompression) == null ? void 0 : _a.useWebWorker) ?? false
16868
- });
16869
- screenshotUrls = uploadedUrls.filter(
16870
- (url) => typeof url === "string" && url.length > 0
16871
- );
16872
- }
16873
- }
17549
+ const videoFile2 = data.videoFile ?? data.position.videoFile;
17550
+ const authInfo = getAuthInfo();
16874
17551
  const allNetworkLogs = await fetchNetworkLogs();
16875
17552
  const allConsoleLogs = await fetchConsoleLogs();
16876
17553
  const filteredNetworkLogs = filterExcludeServiceRelatedLogs(allNetworkLogs);
@@ -16878,100 +17555,141 @@ function useIssueSubmission({
16878
17555
  const limitedNetworkLogs = selectNetworkLogsForSubmit(filteredNetworkLogs);
16879
17556
  const networkLogs = compactNetworkLogsForTransport(limitedNetworkLogs) ?? [];
16880
17557
  const consoleLogs = filteredConsoleLogs.slice(-CONSOLE_LOG_SUBMIT_LIMIT);
17558
+ const socketConnections = readSocketConnections();
16881
17559
  const positionData = data.position.tracked ?? getElementPosition(data.position.element, {
16882
17560
  clientX: data.position.x - window.scrollX,
16883
17561
  clientY: data.position.y - window.scrollY
16884
17562
  });
16885
- const authInfo = getAuthInfo();
16886
- const userAgent = navigator.userAgent;
16887
- const currentUrl = getCurrentPageUrl();
16888
- const issueData = {
16889
- url: currentUrl,
16890
- title: ((_b2 = data.title) == null ? void 0 : _b2.trim()) || void 0,
16891
- description: ((_c2 = data.description) == null ? void 0 : _c2.trim()) || void 0,
16892
- intent: ((_d2 = data.intent) == null ? void 0 : _d2.trim()) || void 0,
16893
- position: {
16894
- x: data.position.x,
16895
- y: data.position.y,
16896
- selector: positionData.selector,
16897
- xpath: positionData.xpath,
16898
- viewport: positionData.viewport,
16899
- elementRect: positionData.elementRect,
16900
- locator: positionData.locator,
16901
- component: positionData.component
16902
- },
16903
- screenshot: screenshotUrls && screenshotUrls.length > 0 ? screenshotUrls : void 0,
16904
- video: videoUrl,
16905
- network_logs: networkLogs,
16906
- console_logs: consoleLogs.length > 0 ? consoleLogs : void 0,
16907
- user_agent: userAgent,
16908
- tenant_id: authInfo.tenantId,
16909
- project_id: authInfo.projectId,
16910
- account_id: authInfo.accountId || void 0,
16911
- snippet_id: snippetId ?? void 0,
16912
- ...buildExternalUserPayload(SDKManager.getInstance().config.user),
16913
- // 비어 있는 값은 보내지 않는다 — 저장해봐야 값 없음과 같고 요청만 커진다.
16914
- custom_field_values: Object.entries(customFieldValues ?? {}).filter(([, value]) => value !== null && value !== void 0 && value !== "").map(([field_definition_id, value]) => ({ field_definition_id, value }))
16915
- };
16916
- if (((_e2 = issueData.custom_field_values) == null ? void 0 : _e2.length) === 0) {
16917
- delete issueData.custom_field_values;
16918
- }
16919
- if (sessionReplay == null ? void 0 : sessionReplay.canAttach) {
16920
- try {
16921
- const snapshot = await sessionReplay.takeSnapshot();
16922
- if (snapshot) {
16923
- const uploaded = await uploadSessionReplay(snapshot);
16924
- if (uploaded) {
16925
- issueData.session_replay_upload_id = uploaded.uploadId;
16926
- issueData.session_replay_meta = uploaded.meta;
16927
- }
16928
- }
16929
- } catch (replayError) {
16930
- devLogger.error(
16931
- "useIssueSubmission",
16932
- "세션 리플레이 첨부 실패 (이슈 생성 계속):",
16933
- replayError
16934
- );
16935
- }
16936
- }
16937
- try {
16938
- const socketSnapshot = await serializeSocketTranscript(
16939
- getCollectorSession().socket.getConnections()
16940
- );
16941
- if (socketSnapshot) {
16942
- const uploaded = await uploadSocketTranscript(
16943
- { post: (endpoint, body) => apiClient.post(endpoint, body), directUpload },
16944
- socketSnapshot
16945
- );
16946
- if (uploaded) {
16947
- issueData.socket_log_upload_id = uploaded.uploadId;
16948
- }
16949
- }
16950
- } catch (socketError) {
16951
- devLogger.error("useIssueSubmission", "소켓 전사 첨부 실패 (이슈 생성 계속):", socketError);
16952
- }
16953
- const { networkLogs: fittedNetworkLogs, stillOverBudget } = shrinkNetworkLogsToFit(
16954
- issueData.network_logs ?? [],
16955
- measureJsonBytes({ ...issueData, network_logs: void 0 })
16956
- );
16957
- issueData.network_logs = fittedNetworkLogs.length > 0 ? fittedNetworkLogs : void 0;
16958
- if (stillOverBudget) {
16959
- throw new Error(
16960
- "이슈 데이터가 전송 한도를 넘어 저장할 수 없습니다. 설명을 줄이거나 첨부를 빼고 다시 시도해주세요."
16961
- );
16962
- }
16963
17563
  const integrations = buildIssueIntegrationsPayload({
16964
17564
  jiraEnabled: jiraService.isConfigured(),
16965
17565
  jiraFields: data.jiraFields,
16966
17566
  notionEnabled: activeIntegrationType === "NOTION" && Boolean(notionIntegrationId),
16967
17567
  notionProperties: data.notionFields
16968
17568
  });
16969
- const createdIssue = await issueService.createIssue({ ...issueData, integrations });
17569
+ const { createdIssue, attachments, excludeVideo, screenshotExcluded } = await runMaskedSubmit(
17570
+ {
17571
+ source: widgetMaskingPolicySource,
17572
+ projectId: authInfo.projectId,
17573
+ prepareScreen: (context) => {
17574
+ var _a2;
17575
+ return prepareIssueScreenAttachments({
17576
+ context,
17577
+ position: data.position,
17578
+ // 정책이 영상을 빼면 이슈에 화면 근거가 남도록 스크린샷을 대신 올린다.
17579
+ includeScreenshots: !videoFile2 || Boolean((_a2 = context.rules) == null ? void 0 : _a2.screen.exclude_video),
17580
+ uploadScreenshots: (dataUrls) => {
17581
+ var _a3;
17582
+ return uploadScreenshots2(dataUrls, {
17583
+ useWebWorker: ((_a3 = SDKManager.getInstance().config.screenshotCompression) == null ? void 0 : _a3.useWebWorker) ?? false
17584
+ });
17585
+ },
17586
+ sessionReplay,
17587
+ recaptureScreenshots
17588
+ });
17589
+ },
17590
+ submit: async (context, screen) => {
17591
+ var _a2, _b2, _c2, _d2, _e2, _f2;
17592
+ const masked = applySubmitMasking({
17593
+ snapshot: context.snapshot,
17594
+ projectId: authInfo.projectId,
17595
+ networkLogs,
17596
+ consoleLogs,
17597
+ socketConnections,
17598
+ pageUrl: getCurrentPageUrl()
17599
+ });
17600
+ const excludeVideo2 = Boolean(videoFile2 && ((_a2 = masked.rules) == null ? void 0 : _a2.screen.exclude_video));
17601
+ const maskSelectors = ((_b2 = context.rules) == null ? void 0 : _b2.screen.mask_selectors) ?? [];
17602
+ const issueData = {
17603
+ url: masked.pageUrl,
17604
+ title: ((_c2 = data.title) == null ? void 0 : _c2.trim()) || void 0,
17605
+ // 마스킹 정책 때문에 버린 스크린샷은 이슈에도 남긴다. 말없이 빼지 않는다.
17606
+ description: screen.screenshotExcluded ? appendMaskingNotice(
17607
+ data.description,
17608
+ screenshotExclusionNotice(screen.screenshotExcluded)
17609
+ ) : ((_d2 = data.description) == null ? void 0 : _d2.trim()) || void 0,
17610
+ intent: ((_e2 = data.intent) == null ? void 0 : _e2.trim()) || void 0,
17611
+ position: {
17612
+ x: data.position.x,
17613
+ y: data.position.y,
17614
+ xpath: positionData.xpath,
17615
+ viewport: positionData.viewport,
17616
+ elementRect: positionData.elementRect,
17617
+ // 대상 규칙이 가린 요소의 페이지 텍스트는 selector·위치 지문에도 싣지 않는다.
17618
+ ...maskPositionForSubmit(positionData, data.position.element, maskSelectors),
17619
+ component: positionData.component
17620
+ },
17621
+ screenshot: screen.screenshotUrls.length > 0 ? screen.screenshotUrls : void 0,
17622
+ ...videoFile2 && !excludeVideo2 ? { video_pending: true } : {},
17623
+ network_logs: masked.networkLogs,
17624
+ console_logs: masked.consoleLogs.length > 0 ? masked.consoleLogs : void 0,
17625
+ user_agent: navigator.userAgent,
17626
+ tenant_id: authInfo.tenantId,
17627
+ project_id: authInfo.projectId,
17628
+ account_id: authInfo.accountId || void 0,
17629
+ snippet_id: snippetId ?? void 0,
17630
+ masking_policy_version: masked.version,
17631
+ ...buildExternalUserPayload(SDKManager.getInstance().config.user),
17632
+ // 비어 있는 값은 보내지 않는다 — 저장해봐야 값 없음과 같고 요청만 커진다.
17633
+ custom_field_values: Object.entries(customFieldValues ?? {}).filter(([, value]) => value !== null && value !== void 0 && value !== "").map(([field_definition_id, value]) => ({ field_definition_id, value }))
17634
+ };
17635
+ if (((_f2 = issueData.custom_field_values) == null ? void 0 : _f2.length) === 0) {
17636
+ delete issueData.custom_field_values;
17637
+ }
17638
+ const replay = screen.replay;
17639
+ const attachments2 = await collectAttachmentUploads({
17640
+ takeReplay: replay ? async () => replay : void 0,
17641
+ getConnections: () => masked.socketConnections,
17642
+ onError: (kind, error) => devLogger.error(
17643
+ "useIssueSubmission",
17644
+ kind === "replay" ? "세션 리플레이 스냅샷 실패 (이슈 생성 계속):" : "소켓 전사 직렬화 실패 (이슈 생성 계속):",
17645
+ error
17646
+ )
17647
+ });
17648
+ if (hasReplayUpload(attachments2)) {
17649
+ issueData.session_replay_pending = true;
17650
+ }
17651
+ const { networkLogs: fittedNetworkLogs, stillOverBudget } = shrinkNetworkLogsToFit(
17652
+ issueData.network_logs ?? [],
17653
+ measureJsonBytes({ ...issueData, network_logs: void 0 })
17654
+ );
17655
+ issueData.network_logs = fittedNetworkLogs.length > 0 ? fittedNetworkLogs : void 0;
17656
+ if (stillOverBudget) {
17657
+ throw new Error(
17658
+ "이슈 데이터가 전송 한도를 넘어 저장할 수 없습니다. 설명을 줄이거나 첨부를 빼고 다시 시도해주세요."
17659
+ );
17660
+ }
17661
+ const createdIssue2 = await issueService.createIssue({ ...issueData, integrations });
17662
+ restartReplayStartedWithoutPolicy(context, sessionReplay);
17663
+ return {
17664
+ createdIssue: createdIssue2,
17665
+ attachments: attachments2,
17666
+ excludeVideo: excludeVideo2,
17667
+ screenshotExcluded: screen.screenshotExcluded
17668
+ };
17669
+ }
17670
+ }
17671
+ );
17672
+ maskingOutcomeRef.current = {
17673
+ videoExcluded: excludeVideo,
17674
+ videoExclusionNotified: Boolean(videoExclusionNotified),
17675
+ ...screenshotExcluded ? { screenshotExcluded } : {}
17676
+ };
17677
+ const uploadVideo = videoFile2 && !excludeVideo ? videoFile2 : null;
17678
+ if (uploadVideo || attachments.length > 0) {
17679
+ await enqueueIssueUploads({
17680
+ videoQueue: getWidgetVideoUploadQueue(),
17681
+ attachmentQueue: getWidgetAttachmentUploadQueue(),
17682
+ issueId: createdIssue.id,
17683
+ projectKey: authInfo.projectId,
17684
+ video: uploadVideo ? { blob: uploadVideo, mimeType: normalizeUploadMimeType(uploadVideo.type, "video") } : null,
17685
+ attachments
17686
+ });
17687
+ }
16970
17688
  for (const service of createdIssue.failed_services ?? []) {
16971
17689
  toast.error(getIntegrationFailureMessage(service));
16972
17690
  }
16973
17691
  failedServicesRef.current = createdIssue.failed_services ?? [];
16974
- if (((_f2 = data.intent) == null ? void 0 : _f2.trim()) && createdIssue.generation_status === "skipped") {
17692
+ if (((_a = data.intent) == null ? void 0 : _a.trim()) && createdIssue.generation_status === "skipped") {
16975
17693
  toast.warning(ISSUE_DRAFT_DISABLED_MESSAGE);
16976
17694
  }
16977
17695
  return createdIssue;
@@ -16980,7 +17698,17 @@ function useIssueSubmission({
16980
17698
  queryClient.invalidateQueries({ queryKey: ["contentIssues"] });
16981
17699
  SDKManager.getInstance().eventBus.emit("issue:created", createdIssue);
16982
17700
  },
16983
- onError: (error) => {
17701
+ onError: (error, data) => {
17702
+ const masking = describeMaskingSubmitFailure(error, {
17703
+ replayRecording: Boolean(sessionReplay == null ? void 0 : sessionReplay.isRecording()),
17704
+ screenshotsRecaptured: !data.position.videoFile
17705
+ });
17706
+ if (masking) {
17707
+ toast.error(masking.message, { title: masking.title, duration: 0 });
17708
+ if (masking.screenRulesChanged) onScreenRulesChanged == null ? void 0 : onScreenRulesChanged();
17709
+ if (masking.restartReplay) void (sessionReplay == null ? void 0 : sessionReplay.restartRecording());
17710
+ return;
17711
+ }
16984
17712
  const billing = classifyBillingError(error);
16985
17713
  if ((billing == null ? void 0 : billing.kind) === "quota_exceeded") {
16986
17714
  toast.error("지금은 이슈를 제출할 수 없습니다. 잠시 후 다시 시도해주세요.");
@@ -16992,6 +17720,7 @@ function useIssueSubmission({
16992
17720
  const doSubmit = useCallback(
16993
17721
  async ({ title, description, intent }) => {
16994
17722
  failedServicesRef.current = [];
17723
+ maskingOutcomeRef.current = NO_MASKING_OUTCOME;
16995
17724
  await createIssueMutation.mutateAsync({
16996
17725
  title,
16997
17726
  description,
@@ -17001,15 +17730,16 @@ function useIssueSubmission({
17001
17730
  notionFields,
17002
17731
  videoFile
17003
17732
  });
17004
- onSubmit(failedServicesRef.current.length > 0);
17733
+ onSubmit(failedServicesRef.current.length > 0, maskingOutcomeRef.current);
17005
17734
  },
17006
17735
  [position2, onSubmit, jiraFields, notionFields, videoFile, createIssueMutation]
17007
17736
  );
17008
17737
  const { execute: submitIssue, isExecuting: isSubmitting } = useGuardedCallback(doSubmit);
17738
+ const submissionError = createIssueMutation.error;
17009
17739
  return {
17010
17740
  submitIssue,
17011
17741
  isSubmitting,
17012
- error: createIssueMutation.error ? `이슈 생성에 실패했습니다: ${createIssueMutation.error instanceof Error ? createIssueMutation.error.message : "알 수 없는 오류"}` : null
17742
+ error: submissionError && !describeMaskingSubmitFailure(submissionError) ? `이슈 생성에 실패했습니다: ${submissionError instanceof Error ? submissionError.message : "알 수 없는 오류"}` : null
17013
17743
  };
17014
17744
  }
17015
17745
  async function fetchSnippets(projectId) {
@@ -17147,11 +17877,17 @@ function mergeSnippetIntoDescription(currentDescription, prevBody, nextBody) {
17147
17877
  const base = prevBody ? trimmedCurrent.slice(0, trimmedCurrent.length - prevBody.length).trimEnd() : currentDescription;
17148
17878
  return { ref: normalizedNext, description: appendSnippet(base, nextBody) };
17149
17879
  }
17150
- function useSessionReplayController() {
17880
+ function useSessionReplayController(identity = null) {
17151
17881
  const controllerRef = useRef(null);
17152
17882
  if (!controllerRef.current) {
17153
17883
  controllerRef.current = new SessionReplayCaptureController({
17154
- loadCapabilities: () => apiClient.get("/api/session-replays/capabilities")
17884
+ loadCapabilities: () => apiClient.get("/api/session-replays/capabilities"),
17885
+ // 키에 묶인 프로젝트 정책의 선택자를 메모한다. 위젯은 그 프로젝트로만 제출한다.
17886
+ maskingPolicy: {
17887
+ source: widgetMaskingPolicySource,
17888
+ pendingRefresh: pendingWidgetMaskingPolicyRefresh,
17889
+ refreshedWithin: widgetMaskingPolicyRefreshedWithin
17890
+ }
17155
17891
  });
17156
17892
  }
17157
17893
  const controller = controllerRef.current;
@@ -17164,15 +17900,30 @@ function useSessionReplayController() {
17164
17900
  controller.destroy();
17165
17901
  };
17166
17902
  }, [controller]);
17167
- const takeSnapshot = useCallback(async () => {
17168
- return controller.takeSnapshot();
17169
- }, [controller]);
17903
+ const startedIdentityRef = useRef(identity);
17904
+ useEffect(() => {
17905
+ if (startedIdentityRef.current === identity) return;
17906
+ startedIdentityRef.current = identity;
17907
+ void controller.restartRecording();
17908
+ }, [controller, identity]);
17909
+ const takeSnapshot = useCallback(
17910
+ async (maskSelectors) => controller.takeSnapshot(maskSelectors),
17911
+ [controller]
17912
+ );
17913
+ const isRecording = useCallback(() => controller.isRecording(), [controller]);
17914
+ const getMaskMemoSelectors = useCallback(() => controller.getMaskMemoSelectors(), [controller]);
17915
+ const whenRestarted = useCallback(() => controller.whenRestarted(), [controller]);
17916
+ const restartRecording = useCallback(() => controller.restartRecording(), [controller]);
17170
17917
  const markReportStart = useCallback(() => controller.markReportStart(), [controller]);
17171
17918
  const clearReportStart = useCallback(() => controller.clearReportStart(), [controller]);
17172
17919
  return {
17173
17920
  available: snapshot.available,
17174
17921
  canAttach: snapshot.canAttach,
17922
+ isRecording,
17175
17923
  takeSnapshot,
17924
+ getMaskMemoSelectors,
17925
+ whenRestarted,
17926
+ restartRecording,
17176
17927
  markReportStart,
17177
17928
  clearReportStart
17178
17929
  };
@@ -17180,7 +17931,13 @@ function useSessionReplayController() {
17180
17931
  const DISABLED_CONTROLLER = {
17181
17932
  available: false,
17182
17933
  canAttach: false,
17934
+ isRecording: () => false,
17183
17935
  takeSnapshot: async () => null,
17936
+ getMaskMemoSelectors: () => null,
17937
+ whenRestarted: async () => {
17938
+ },
17939
+ restartRecording: async () => {
17940
+ },
17184
17941
  markReportStart: () => {
17185
17942
  },
17186
17943
  clearReportStart: () => {
@@ -17188,7 +17945,8 @@ const DISABLED_CONTROLLER = {
17188
17945
  };
17189
17946
  const SessionReplayContext = createContext(null);
17190
17947
  function SessionReplayProvider({ children }) {
17191
- const controller = useSessionReplayController();
17948
+ const { tenantId, projectId } = useAuthContext();
17949
+ const controller = useSessionReplayController(`${tenantId ?? ""}:${projectId ?? ""}`);
17192
17950
  return /* @__PURE__ */ jsx(SessionReplayContext.Provider, { value: controller, children });
17193
17951
  }
17194
17952
  function useSessionReplay() {
@@ -17443,7 +18201,7 @@ function useCustomFieldValueDraft({
17443
18201
  }, []);
17444
18202
  const setValue = useCallback(
17445
18203
  (fieldDefinitionId, value) => {
17446
- setDraft((current) => ({ ...current, [fieldDefinitionId]: value }));
18204
+ setDraft((current2) => ({ ...current2, [fieldDefinitionId]: value }));
17447
18205
  clearTimeout(timersRef.current[fieldDefinitionId]);
17448
18206
  timersRef.current[fieldDefinitionId] = setTimeout(() => {
17449
18207
  delete timersRef.current[fieldDefinitionId];
@@ -17608,6 +18366,7 @@ function IssueFormError({ error }) {
17608
18366
  return /* @__PURE__ */ jsx("div", { className: "rounded-[18px] border border-danger/20 bg-danger-subtle px-4 py-3 text-sm text-danger", children: error });
17609
18367
  }
17610
18368
  const SWITCH_TO_AI = "AI 작성으로 돌아가기";
18369
+ const SWITCH_TO_AI_HINT = "기대한 동작만 적으면 AI가 제목과 설명을 작성합니다.";
17611
18370
  function IssueFormHeader({ title, onClose, onSwitchToAi }) {
17612
18371
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
17613
18372
  /* @__PURE__ */ jsx("h2", { className: "m-0 text-lg font-semibold text-text-primary", children: title }),
@@ -17624,7 +18383,10 @@ function IssueFormHeader({ title, onClose, onSwitchToAi }) {
17624
18383
  children: /* @__PURE__ */ jsx(Sparkles, { className: "h-4 w-4" })
17625
18384
  }
17626
18385
  ) }),
17627
- /* @__PURE__ */ jsx(TooltipContent, { side: "top", sideOffset: 10, children: SWITCH_TO_AI })
18386
+ /* @__PURE__ */ jsxs(TooltipContent, { side: "top", sideOffset: 10, className: "max-w-[16rem]", children: [
18387
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: SWITCH_TO_AI }),
18388
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 opacity-80", children: SWITCH_TO_AI_HINT })
18389
+ ] })
17628
18390
  ] }) }) : null,
17629
18391
  /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: onClose, "aria-label": "닫기", children: /* @__PURE__ */ jsx(X$1, { className: "h-4 w-4" }) })
17630
18392
  ] })
@@ -17717,7 +18479,7 @@ function IssueFormJiraFields({
17717
18479
  const handleIssueTypeChange = (nextIssueTypeId) => {
17718
18480
  if (nextIssueTypeId === issueTypeId) return;
17719
18481
  onValuesReplace == null ? void 0 : onValuesReplace(
17720
- (current) => switchJiraIssueType(current, {
18482
+ (current2) => switchJiraIssueType(current2, {
17721
18483
  exposedFields,
17722
18484
  previousFields: fieldDefinitions,
17723
18485
  nextFields: pickJiraIssueTypeFields(fields, issueTypes, nextIssueTypeId),
@@ -17852,12 +18614,35 @@ function SessionReplayLimitNotice({
17852
18614
  }
17853
18615
  return /* @__PURE__ */ jsx("p", { className: "text-xs text-warning", children: "이번 달 리플레이 한도를 모두 사용했습니다. 다음 달에 한도가 초기화되거나, 플랜을 업그레이드하면 다시 첨부할 수 있습니다." });
17854
18616
  }
18617
+ function MaskingVideoExcludedNotice({ excluded }) {
18618
+ if (!excluded) {
18619
+ return null;
18620
+ }
18621
+ return /* @__PURE__ */ jsx("p", { className: "text-xs text-warning", children: MASKING_COPY.videoExcluded });
18622
+ }
18623
+ function useMaskingVideoExcluded(source, projectId, hasVideo) {
18624
+ var _a;
18625
+ const { data: snapshot } = useQuery({
18626
+ queryKey: ["maskingPolicySnapshot"],
18627
+ queryFn: () => loadMaskingPolicyForSubmit(source).catch(() => null),
18628
+ enabled: hasVideo,
18629
+ // 워크스페이스를 바꾼 뒤 지난 워크스페이스 스냅샷이나 실패(null)를 신선한 값으로 읽으면 안내가
18630
+ // 틀려 영상이 말없이 빠진다. 앱 전역 QueryClient 의 staleTime·gcTime 과 무관하게 마운트마다 새로 받는다.
18631
+ staleTime: 0,
18632
+ gcTime: 0,
18633
+ refetchOnMount: "always"
18634
+ });
18635
+ if (!hasVideo || !projectId || !snapshot) return false;
18636
+ return ((_a = resolveEffectivePolicy(snapshot, projectId)) == null ? void 0 : _a.screen.exclude_video) ?? false;
18637
+ }
17855
18638
  function IssueForm({
17856
18639
  position: position2,
17857
18640
  onSubmit,
17858
18641
  onCancel,
17859
18642
  activeIntegration,
17860
- isIntegrationLoading = false
18643
+ isIntegrationLoading = false,
18644
+ onScreenRulesChanged,
18645
+ recaptureScreenshots
17861
18646
  }) {
17862
18647
  var _a, _b2;
17863
18648
  const { isMember } = useAuthContext();
@@ -17918,18 +18703,33 @@ function IssueForm({
17918
18703
  const integrationFieldSkeletonCount = Math.max(jiraService.getExposedFields().length, 2);
17919
18704
  const submitSucceededRef = useRef(false);
17920
18705
  const hadIntegrationFailureRef = useRef(false);
18706
+ const maskingOutcomeRef = useRef(NO_MASKING_OUTCOME);
18707
+ const videoExcludedByPolicy = useMaskingVideoExcluded(
18708
+ widgetMaskingPolicySource,
18709
+ selectedProjectId ?? null,
18710
+ Boolean(position2.videoFile)
18711
+ );
18712
+ const handleScreenRulesChanged = () => {
18713
+ onScreenRulesChanged == null ? void 0 : onScreenRulesChanged();
18714
+ void sessionReplay.restartRecording();
18715
+ };
17921
18716
  const issueSubmission = useIssueSubmission({
17922
18717
  position: position2,
17923
- onSubmit: (hasIntegrationFailure) => {
18718
+ onScreenRulesChanged: handleScreenRulesChanged,
18719
+ recaptureScreenshots,
18720
+ onSubmit: (hasIntegrationFailure, outcome) => {
17924
18721
  void snippets.rememberSelection();
17925
18722
  if (formState.mode === "ai") {
17926
- onSubmit(hasIntegrationFailure);
18723
+ onSubmit(hasIntegrationFailure, outcome);
17927
18724
  return;
17928
18725
  }
17929
18726
  submitSucceededRef.current = true;
17930
18727
  hadIntegrationFailureRef.current = hasIntegrationFailure;
18728
+ maskingOutcomeRef.current = outcome;
17931
18729
  setIsOpen(false);
17932
18730
  },
18731
+ // 녹화 제외 안내는 직접 작성 폼에만 있다. AI 작성 말풍선으로 냈으면 완료 토스트가 알린다.
18732
+ videoExclusionNotified: formState.mode === "manual" && videoExcludedByPolicy,
17933
18733
  jiraFields,
17934
18734
  notionFields,
17935
18735
  customFieldValues,
@@ -18008,14 +18808,14 @@ function IssueForm({
18008
18808
  IssueFormJiraFieldDefaults,
18009
18809
  {
18010
18810
  integrationId: jiraIntegrationId,
18011
- onDefaultsResolved: (defaults) => setJiraFields((current) => ({ ...defaults, ...current }))
18811
+ onDefaultsResolved: (defaults) => setJiraFields((current2) => ({ ...defaults, ...current2 }))
18012
18812
  }
18013
18813
  ) }) : null,
18014
18814
  showCustomFields ? /* @__PURE__ */ jsx(QueryBoundary, { loadingFallback: null, onError: () => null, children: /* @__PURE__ */ jsx(
18015
18815
  IssueFormCustomFieldDefaults,
18016
18816
  {
18017
18817
  projectId: selectedProjectId,
18018
- onDefaultsResolved: (defaults) => setCustomFieldValues((current) => ({ ...defaults, ...current }))
18818
+ onDefaultsResolved: (defaults) => setCustomFieldValues((current2) => ({ ...defaults, ...current2 }))
18019
18819
  }
18020
18820
  ) }) : null,
18021
18821
  /* @__PURE__ */ jsx(QueryBoundary, { loadingFallback: null, onError: () => null, children: /* @__PURE__ */ jsx(IssueFormDraftCapabilityGate, { onResolved: handleDraftCapabilityResolved }) }),
@@ -18059,7 +18859,7 @@ function IssueForm({
18059
18859
  return;
18060
18860
  }
18061
18861
  if (submitSucceededRef.current) {
18062
- onSubmit(hadIntegrationFailureRef.current);
18862
+ onSubmit(hadIntegrationFailureRef.current, maskingOutcomeRef.current);
18063
18863
  } else {
18064
18864
  onCancel();
18065
18865
  }
@@ -18088,6 +18888,7 @@ function IssueForm({
18088
18888
  canAttach: sessionReplay.canAttach
18089
18889
  }
18090
18890
  ) : null,
18891
+ /* @__PURE__ */ jsx(MaskingVideoExcludedNotice, { excluded: videoExcludedByPolicy }),
18091
18892
  /* @__PURE__ */ jsx(
18092
18893
  IssueFormTitleField,
18093
18894
  {
@@ -18117,12 +18918,12 @@ function IssueForm({
18117
18918
  values: jiraFields,
18118
18919
  disabled: issueSubmission.isSubmitting,
18119
18920
  onChange: (fieldId, value) => {
18120
- setJiraFields((current) => ({
18121
- ...current,
18921
+ setJiraFields((current2) => ({
18922
+ ...current2,
18122
18923
  [fieldId]: value
18123
18924
  }));
18124
18925
  },
18125
- onDefaultsResolved: (defaults) => setJiraFields((current) => ({ ...defaults, ...current })),
18926
+ onDefaultsResolved: (defaults) => setJiraFields((current2) => ({ ...defaults, ...current2 })),
18126
18927
  onValuesReplace: setJiraFields
18127
18928
  }
18128
18929
  )
@@ -18146,8 +18947,8 @@ function IssueForm({
18146
18947
  values: notionFields,
18147
18948
  disabled: issueSubmission.isSubmitting,
18148
18949
  onChange: (fieldId, value) => {
18149
- setNotionFields((current) => ({
18150
- ...current,
18950
+ setNotionFields((current2) => ({
18951
+ ...current2,
18151
18952
  [fieldId]: value
18152
18953
  }));
18153
18954
  }
@@ -18161,8 +18962,8 @@ function IssueForm({
18161
18962
  projectId: selectedProjectId,
18162
18963
  values: customFieldValues,
18163
18964
  disabled: issueSubmission.isSubmitting,
18164
- onChange: (fieldId, value) => setCustomFieldValues((current) => ({ ...current, [fieldId]: value })),
18165
- onDefaultsResolved: (defaults) => setCustomFieldValues((current) => ({ ...defaults, ...current }))
18965
+ onChange: (fieldId, value) => setCustomFieldValues((current2) => ({ ...current2, [fieldId]: value })),
18966
+ onDefaultsResolved: (defaults) => setCustomFieldValues((current2) => ({ ...defaults, ...current2 }))
18166
18967
  }
18167
18968
  ) }) : null
18168
18969
  ] }),
@@ -18175,17 +18976,17 @@ function IssueForm({
18175
18976
  getPortalContainer()
18176
18977
  );
18177
18978
  }
18178
- function invalidateIssueQueries(issueId) {
18179
- issueService.invalidateCache();
18180
- queryClient.invalidateQueries({ queryKey: ["contentIssues"] });
18181
- queryClient.invalidateQueries({ queryKey: ["issue", issueId] });
18182
- }
18183
- function notifyIssueUpdated(issueId) {
18184
- SDKManager.getInstance().eventBus.emit("issue:updated", issueId);
18185
- }
18186
- function applyIssueMutationEffects(issueId) {
18187
- invalidateIssueQueries(issueId);
18188
- notifyIssueUpdated(issueId);
18979
+ function descriptionExternalUrl(issue, serviceType, jiraSiteUrl) {
18980
+ var _a, _b2;
18981
+ if (serviceType === "NOTION") {
18982
+ return ((_a = getNotionPageMetadata(getPrimaryNotionMapping(issue))) == null ? void 0 : _a.notion_page_url) ?? null;
18983
+ }
18984
+ if (serviceType === "JIRA") {
18985
+ const key = (_b2 = getJiraMetadata(getPrimaryJiraMapping(issue))) == null ? void 0 : _b2.jira_key;
18986
+ const site = jiraSiteUrl == null ? void 0 : jiraSiteUrl.replace(/\/+$/, "");
18987
+ return key && site ? `${site}/browse/${key}` : null;
18988
+ }
18989
+ return null;
18189
18990
  }
18190
18991
  function useJiraIssueSync(issue) {
18191
18992
  const [isSyncingAll, setIsSyncingAll] = useState(false);
@@ -18220,6 +19021,39 @@ function useJiraIssueSync(issue) {
18220
19021
  canSync
18221
19022
  };
18222
19023
  }
19024
+ const REFETCH_STATUSES = /* @__PURE__ */ new Set([400, 404, 409]);
19025
+ function useRefreshIssueDescription(issueId, issueQueryKey, onUpdated) {
19026
+ const queryClient2 = useQueryClient();
19027
+ const reload = useCallback(async () => {
19028
+ await queryClient2.cancelQueries({ queryKey: issueQueryKey, exact: true });
19029
+ return queryClient2.fetchQuery({
19030
+ queryKey: issueQueryKey,
19031
+ queryFn: () => issueService.getIssue(issueId),
19032
+ staleTime: 0
19033
+ });
19034
+ }, [issueId, issueQueryKey, queryClient2]);
19035
+ const refresh = useCallback(async () => {
19036
+ try {
19037
+ await issueService.refreshIssueDescription(issueId);
19038
+ } catch (error) {
19039
+ const status = error == null ? void 0 : error.status;
19040
+ if (status !== void 0 && REFETCH_STATUSES.has(status)) invalidateIssueQueries(issueId);
19041
+ throw error;
19042
+ }
19043
+ let fresh;
19044
+ try {
19045
+ fresh = await reload();
19046
+ } catch (error) {
19047
+ invalidateIssueQueries(issueId);
19048
+ throw error;
19049
+ }
19050
+ queryClient2.invalidateQueries({ queryKey: ["contentIssues"] });
19051
+ notifyIssueUpdated(issueId);
19052
+ onUpdated == null ? void 0 : onUpdated();
19053
+ return fresh;
19054
+ }, [issueId, onUpdated, queryClient2, reload]);
19055
+ return { refresh, reload };
19056
+ }
18223
19057
  const __vite_import_meta_env__ = { "BASE_URL": "/", "DEV": false, "MODE": "production", "PROD": true, "SSR": false, "VITE_ADMIN_BASE_URL": "https://app.issuesticker.com", "VITE_API_BASE_URL": "https://api.issuesticker.com" };
18224
19058
  function readViteEnv(name2) {
18225
19059
  var _a;
@@ -18770,12 +19604,13 @@ function ResizableSplitPane({
18770
19604
  var _a;
18771
19605
  event.preventDefault();
18772
19606
  (_a = dragCleanupRef.current) == null ? void 0 : _a.call(dragCleanupRef);
19607
+ const releaseScopes = /* @__PURE__ */ new Set([document, event.currentTarget.getRootNode()]);
18773
19608
  const handleDocumentMouseMove = (moveEvent) => {
18774
19609
  updateRatio(moveEvent.clientX);
18775
19610
  };
18776
19611
  const stopDragging = () => {
18777
19612
  document.removeEventListener("mousemove", handleDocumentMouseMove);
18778
- document.removeEventListener("mouseup", stopDragging);
19613
+ releaseScopes.forEach((scope) => scope.removeEventListener("mouseup", stopDragging));
18779
19614
  window.removeEventListener("blur", stopDragging);
18780
19615
  if (dragCleanupRef.current === stopDragging) {
18781
19616
  dragCleanupRef.current = null;
@@ -18783,7 +19618,7 @@ function ResizableSplitPane({
18783
19618
  };
18784
19619
  dragCleanupRef.current = stopDragging;
18785
19620
  document.addEventListener("mousemove", handleDocumentMouseMove);
18786
- document.addEventListener("mouseup", stopDragging);
19621
+ releaseScopes.forEach((scope) => scope.addEventListener("mouseup", stopDragging));
18787
19622
  window.addEventListener("blur", stopDragging);
18788
19623
  },
18789
19624
  [updateRatio]
@@ -19988,6 +20823,132 @@ function ConsoleLogsSection({
19988
20823
  function IssueCreatedAt({ createdAt }) {
19989
20824
  return /* @__PURE__ */ jsx("div", { className: "text-right text-xs text-text-tertiary", children: new Date(createdAt).toLocaleString("ko-KR") });
19990
20825
  }
20826
+ function safeHttpUrl(value) {
20827
+ if (!value) return null;
20828
+ try {
20829
+ const { protocol } = new URL(value);
20830
+ return protocol === "https:" || protocol === "http:" ? value : null;
20831
+ } catch {
20832
+ return null;
20833
+ }
20834
+ }
20835
+ function mergeByLabel(elements) {
20836
+ const counts = /* @__PURE__ */ new Map();
20837
+ for (const { type, count } of elements) {
20838
+ const label = describeUnsupportedElement(type);
20839
+ counts.set(label, (counts.get(label) ?? 0) + count);
20840
+ }
20841
+ return [...counts];
20842
+ }
20843
+ function DescriptionSyncLockNotice({
20844
+ lock,
20845
+ externalUrl,
20846
+ canEdit = true
20847
+ }) {
20848
+ const service = describeSyncService(lock.serviceType);
20849
+ const href = safeHttpUrl(externalUrl);
20850
+ const Icon2 = lock.blocksEditing ? Lock : Info$1;
20851
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 border-t border-line-subtle py-3 text-sm", children: [
20852
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 text-text-primary", children: [
20853
+ /* @__PURE__ */ jsx(Icon2, { className: "mt-0.5 size-4 shrink-0 text-text-tertiary", "aria-hidden": true }),
20854
+ lock.blocksEditing ? /* @__PURE__ */ jsxs("p", { children: [
20855
+ service,
20856
+ "에서 설명 섹션의 제목이 지워져 설명을 동기화할 수 없습니다. ",
20857
+ service,
20858
+ "에서 지운 제목을 되살리면 다시 수정할 수 있습니다."
20859
+ ] }) : /* @__PURE__ */ jsxs("p", { children: [
20860
+ "이 설명에는 ",
20861
+ service,
20862
+ "에서만 고칠 수 있는 요소가 있습니다.",
20863
+ canEdit ? " 나머지 설명은 이곳에서 수정할 수 있습니다." : null
20864
+ ] })
20865
+ ] }),
20866
+ /* @__PURE__ */ jsx("ul", { className: "flex flex-wrap gap-x-3 gap-y-1 pl-6 text-xs text-text-secondary", children: mergeByLabel(lock.elements).map(([label, count]) => /* @__PURE__ */ jsxs("li", { children: [
20867
+ label,
20868
+ " ",
20869
+ count,
20870
+ "개"
20871
+ ] }, label)) }),
20872
+ href ? /* @__PURE__ */ jsxs(
20873
+ "a",
20874
+ {
20875
+ href,
20876
+ target: "_blank",
20877
+ rel: "noopener noreferrer",
20878
+ className: "flex items-center gap-1 self-start pl-6 text-xs text-text-secondary underline-offset-2 hover:underline",
20879
+ children: [
20880
+ service,
20881
+ "에서 열기",
20882
+ /* @__PURE__ */ jsx(ExternalLink$1, { className: "size-3", "aria-hidden": true })
20883
+ ]
20884
+ }
20885
+ ) : null
20886
+ ] });
20887
+ }
20888
+ function HiddenDescriptionNotice({
20889
+ serviceType,
20890
+ kinds,
20891
+ externalUrl
20892
+ }) {
20893
+ const service = describeSyncService(serviceType);
20894
+ const href = safeHttpUrl(externalUrl);
20895
+ const names = describeHiddenKinds(kinds);
20896
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 border-t border-line-subtle py-3 text-sm", children: [
20897
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 text-text-primary", children: [
20898
+ /* @__PURE__ */ jsx(Info$1, { className: "mt-0.5 size-4 shrink-0 text-text-tertiary", "aria-hidden": true }),
20899
+ /* @__PURE__ */ jsxs("p", { children: [
20900
+ service,
20901
+ "에서만 수정할 수 있는 내용",
20902
+ names ? `(${names})` : "",
20903
+ "은 편집에서 숨겼습니다. 저장해도 ",
20904
+ service,
20905
+ "에는 그대로 남습니다."
20906
+ ] })
20907
+ ] }),
20908
+ href ? /* @__PURE__ */ jsxs(
20909
+ "a",
20910
+ {
20911
+ href,
20912
+ target: "_blank",
20913
+ rel: "noopener noreferrer",
20914
+ className: "flex items-center gap-1 self-start pl-6 text-xs text-text-secondary underline-offset-2 hover:underline",
20915
+ children: [
20916
+ service,
20917
+ "에서 열기",
20918
+ /* @__PURE__ */ jsx(ExternalLink$1, { className: "size-3", "aria-hidden": true })
20919
+ ]
20920
+ }
20921
+ ) : null
20922
+ ] });
20923
+ }
20924
+ function DescriptionRefreshNotice({
20925
+ onRefresh,
20926
+ isRefreshing
20927
+ }) {
20928
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 border-t border-line-subtle py-3 text-sm", children: [
20929
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 text-text-primary", children: [
20930
+ /* @__PURE__ */ jsx(Info$1, { className: "mt-0.5 size-4 shrink-0 text-text-tertiary", "aria-hidden": true }),
20931
+ /* @__PURE__ */ jsx("p", { children: "설명을 다시 불러와야 수정할 수 있습니다." })
20932
+ ] }),
20933
+ /* @__PURE__ */ jsx(
20934
+ Button,
20935
+ {
20936
+ type: "button",
20937
+ variant: "secondary",
20938
+ size: "sm",
20939
+ onClick: onRefresh,
20940
+ disabled: isRefreshing,
20941
+ className: "self-start",
20942
+ children: isRefreshing ? "다시 불러오는 중..." : "다시 불러오기"
20943
+ }
20944
+ )
20945
+ ] });
20946
+ }
20947
+ function DescriptionEditBasisChangedNotice({
20948
+ reason = "changed"
20949
+ }) {
20950
+ return /* @__PURE__ */ jsx("p", { role: "status", className: "py-2 text-sm text-text-primary", children: reason === "rejected" ? "이 설명은 숨긴 내용을 함께 표시한 상태에서만 저장할 수 있습니다. 입력한 내용을 복사한 뒤 「취소」를 클릭하고 다시 수정해주세요." : "설명이 바뀌어 지금 입력한 내용은 저장할 수 없습니다. 입력한 내용을 복사한 뒤 「취소」를 클릭하고 다시 수정해주세요." });
20951
+ }
19991
20952
  function ok$1() {
19992
20953
  }
19993
20954
  function unreachable() {
@@ -21232,9 +22193,6 @@ const svg = merge([aria, svg$1, xlink, xmlns, xml], "svg");
21232
22193
  function stringify(values) {
21233
22194
  return values.join(" ").trim();
21234
22195
  }
21235
- function getDefaultExportFromCjs(x) {
21236
- return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
21237
- }
21238
22196
  var cjs$2 = {};
21239
22197
  var cjs$1;
21240
22198
  var hasRequiredCjs$2;
@@ -25662,34 +26620,34 @@ function subcontent(events, eventIndex) {
25662
26620
  let stream;
25663
26621
  let previous2;
25664
26622
  let index2 = -1;
25665
- let current = token;
26623
+ let current2 = token;
25666
26624
  let adjust = 0;
25667
26625
  let start = 0;
25668
26626
  const breaks = [start];
25669
- while (current) {
25670
- while (events.get(++startPosition)[1] !== current) {
26627
+ while (current2) {
26628
+ while (events.get(++startPosition)[1] !== current2) {
25671
26629
  }
25672
26630
  startPositions.push(startPosition);
25673
- if (!current._tokenizer) {
25674
- stream = context.sliceStream(current);
25675
- if (!current.next) {
26631
+ if (!current2._tokenizer) {
26632
+ stream = context.sliceStream(current2);
26633
+ if (!current2.next) {
25676
26634
  stream.push(null);
25677
26635
  }
25678
26636
  if (previous2) {
25679
- tokenizer.defineSkip(current.start);
26637
+ tokenizer.defineSkip(current2.start);
25680
26638
  }
25681
- if (current._isInFirstContentOfListItem) {
26639
+ if (current2._isInFirstContentOfListItem) {
25682
26640
  tokenizer._gfmTasklistFirstContentOfListItem = true;
25683
26641
  }
25684
26642
  tokenizer.write(stream);
25685
- if (current._isInFirstContentOfListItem) {
26643
+ if (current2._isInFirstContentOfListItem) {
25686
26644
  tokenizer._gfmTasklistFirstContentOfListItem = void 0;
25687
26645
  }
25688
26646
  }
25689
- previous2 = current;
25690
- current = current.next;
26647
+ previous2 = current2;
26648
+ current2 = current2.next;
25691
26649
  }
25692
- current = token;
26650
+ current2 = token;
25693
26651
  while (++index2 < childEvents.length) {
25694
26652
  if (
25695
26653
  // Find a void token that includes a break.
@@ -25697,15 +26655,15 @@ function subcontent(events, eventIndex) {
25697
26655
  ) {
25698
26656
  start = index2 + 1;
25699
26657
  breaks.push(start);
25700
- current._tokenizer = void 0;
25701
- current.previous = void 0;
25702
- current = current.next;
26658
+ current2._tokenizer = void 0;
26659
+ current2.previous = void 0;
26660
+ current2 = current2.next;
25703
26661
  }
25704
26662
  }
25705
26663
  tokenizer.events = [];
25706
- if (current) {
25707
- current._tokenizer = void 0;
25708
- current.previous = void 0;
26664
+ if (current2) {
26665
+ current2._tokenizer = void 0;
26666
+ current2.previous = void 0;
25709
26667
  } else {
25710
26668
  breaks.pop();
25711
26669
  }
@@ -31669,7 +32627,7 @@ const deprecations = [
31669
32627
  { from: "transformImageUri", id: "#add-urltransform", to: "urlTransform" },
31670
32628
  { from: "transformLinkUri", id: "#add-urltransform", to: "urlTransform" }
31671
32629
  ];
31672
- function Markdown(options) {
32630
+ function Markdown$1(options) {
31673
32631
  const processor = createProcessor(options);
31674
32632
  const file = createFile(options);
31675
32633
  return post(processor.runSync(processor.parse(file), file), options);
@@ -31766,33 +32724,6 @@ function defaultUrlTransform(value) {
31766
32724
  }
31767
32725
  return "";
31768
32726
  }
31769
- function useIssueFieldUpdate(issueId, onUpdated) {
31770
- const [error, setError] = useState(null);
31771
- const [savingField, setSavingField] = useState(null);
31772
- const updateField = useCallback(
31773
- async (field, value) => {
31774
- setError(null);
31775
- setSavingField(field);
31776
- try {
31777
- await issueService.updateIssue(issueId, { [field]: value });
31778
- applyIssueMutationEffects(issueId);
31779
- onUpdated == null ? void 0 : onUpdated();
31780
- return true;
31781
- } catch (err) {
31782
- setError(err instanceof Error ? err.message : "이슈 업데이트에 실패했습니다.");
31783
- return false;
31784
- } finally {
31785
- setSavingField((current) => current === field ? null : current);
31786
- }
31787
- },
31788
- [issueId, onUpdated]
31789
- );
31790
- return {
31791
- error,
31792
- savingField,
31793
- updateField
31794
- };
31795
- }
31796
32727
  function ccount(value, character) {
31797
32728
  const source = String(value);
31798
32729
  if (typeof character !== "string") {
@@ -34839,9 +35770,20 @@ const markdownComponents = {
34839
35770
  h2: ({ children }) => /* @__PURE__ */ jsx("h2", { className: "my-3 whitespace-pre-wrap text-lg font-semibold text-text-primary", children }),
34840
35771
  h3: ({ children }) => /* @__PURE__ */ jsx("h3", { className: "my-2.5 whitespace-pre-wrap text-base font-semibold text-text-primary", children }),
34841
35772
  p: ({ children }) => /* @__PURE__ */ jsx("p", { className: "my-2 whitespace-pre-wrap text-text-primary", children }),
34842
- ul: ({ children }) => /* @__PURE__ */ jsx("ul", { className: "my-2 list-disc pl-5 text-text-primary", children }),
35773
+ // 노션 할 일 블록은 GFM 작업 목록(`- [ ]`)으로 들어온다. 글머리표 대신 체크박스가 그 자리를 맡는다.
35774
+ ul: ({ children, className }) => (className == null ? void 0 : className.includes("contains-task-list")) ? /* @__PURE__ */ jsx("ul", { className: "my-2 list-none pl-0 text-text-primary [li_&]:pl-5", children }) : /* @__PURE__ */ jsx("ul", { className: "my-2 list-disc pl-5 text-text-primary", children }),
34843
35775
  ol: ({ children }) => /* @__PURE__ */ jsx("ol", { className: "my-2 list-decimal pl-5 text-text-primary", children }),
34844
35776
  li: ({ children }) => /* @__PURE__ */ jsx("li", { className: "my-1", children }),
35777
+ input: ({ type, checked }) => type === "checkbox" ? /* @__PURE__ */ jsx(
35778
+ "input",
35779
+ {
35780
+ type: "checkbox",
35781
+ checked,
35782
+ disabled: true,
35783
+ readOnly: true,
35784
+ className: "mr-2 align-middle accent-brand"
35785
+ }
35786
+ ) : null,
34845
35787
  code: ({ children, className }) => {
34846
35788
  const isInline = !className;
34847
35789
  if (isInline) {
@@ -34858,7 +35800,8 @@ const markdownComponents = {
34858
35800
  // 깊이만큼 밀린다.
34859
35801
  details: ({ children }) => /* @__PURE__ */ jsx("details", { className: "my-2 [&>*:not(summary)]:pl-4", children }),
34860
35802
  summary: ({ children }) => /* @__PURE__ */ jsx("summary", { className: "cursor-pointer text-text-primary marker:text-text-tertiary", children }),
34861
- a: ({ href, children }) => /* @__PURE__ */ jsx(
35803
+ // 노션의 빈 파일·PDF 블록은 `[pdf]()` 로 들어온다. 빈 href 는 현재 페이지로 이동하므로 글자로만 둔다.
35804
+ a: ({ href, children }) => href ? /* @__PURE__ */ jsx(
34862
35805
  "a",
34863
35806
  {
34864
35807
  href,
@@ -34867,7 +35810,7 @@ const markdownComponents = {
34867
35810
  className: "text-brand underline underline-offset-2 hover:text-brand-hover",
34868
35811
  children
34869
35812
  }
34870
- ),
35813
+ ) : /* @__PURE__ */ jsx("span", { className: "text-text-secondary", children }),
34871
35814
  blockquote: ({ children }) => /* @__PURE__ */ jsx("blockquote", { className: "my-2 border-l-4 border-line-strong pl-3 italic text-text-secondary", children }),
34872
35815
  hr: () => /* @__PURE__ */ jsx("hr", { className: "my-4 border-0 border-t border-line" }),
34873
35816
  strong: ({ children }) => /* @__PURE__ */ jsx("strong", { className: "font-semibold", children }),
@@ -34880,29 +35823,300 @@ const markdownComponents = {
34880
35823
  th: ({ children }) => /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left font-semibold text-text-primary", children }),
34881
35824
  td: ({ children }) => /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-text-primary", children })
34882
35825
  };
35826
+ function Markdown({ children }) {
35827
+ return /* @__PURE__ */ jsx(Markdown$1, { remarkPlugins: markdownRemarkPlugins, components: markdownComponents, children });
35828
+ }
35829
+ const HANGUL_START = 44032;
35830
+ function topicParticle(text2) {
35831
+ return (text2.charCodeAt(text2.length - 1) - HANGUL_START) % 28 === 0 ? "는" : "은";
35832
+ }
35833
+ const READ_ONLY_CLASS = "my-2 border-l-2 border-line pl-3 [&_:is(h1,h2,h3,h4,h5,h6,p,li,td,th,summary,blockquote)]:text-text-secondary";
35834
+ function IssueDescriptionView({
35835
+ description,
35836
+ view
35837
+ }) {
35838
+ const pieces = view == null ? void 0 : view.pieces;
35839
+ if (!view || !(pieces == null ? void 0 : pieces.some((piece) => piece.kind === "hidden"))) {
35840
+ return /* @__PURE__ */ jsx(Markdown, { children: description });
35841
+ }
35842
+ const service = describeSyncService(view.serviceType);
35843
+ return /* @__PURE__ */ jsx(Fragment, { children: pieces.map((piece, index2) => {
35844
+ if (piece.kind === "text") {
35845
+ return /* @__PURE__ */ jsx(Fragment$1, { children: /* @__PURE__ */ jsx(Markdown, { children: piece.markdown }) }, index2);
35846
+ }
35847
+ if (!piece.block.viewable) {
35848
+ const kinds = describeHiddenKinds(piece.block.kinds);
35849
+ return /* @__PURE__ */ jsxs("p", { className: "my-2 border-l-2 border-line pl-3 text-sm text-text-tertiary", children: [
35850
+ kinds,
35851
+ topicParticle(kinds),
35852
+ " ",
35853
+ service,
35854
+ "에서 확인할 수 있습니다."
35855
+ ] }, index2);
35856
+ }
35857
+ return /* @__PURE__ */ jsxs("div", { className: READ_ONLY_CLASS, children: [
35858
+ /* @__PURE__ */ jsxs("span", { className: "block text-xs text-text-tertiary", children: [
35859
+ service,
35860
+ "에서만 수정할 수 있습니다"
35861
+ ] }),
35862
+ /* @__PURE__ */ jsx(Markdown, { children: piece.block.markdown })
35863
+ ] }, index2);
35864
+ }) });
35865
+ }
35866
+ const fallbacks = /* @__PURE__ */ new Map();
35867
+ const listeners$1 = /* @__PURE__ */ new Set();
35868
+ function notify$1() {
35869
+ for (const listener of [...listeners$1]) listener();
35870
+ }
35871
+ function markFullTextFallback(issueId, signature) {
35872
+ if (fallbacks.get(issueId) === signature) return;
35873
+ fallbacks.set(issueId, signature);
35874
+ notify$1();
35875
+ }
35876
+ function readFullTextFallback(issueId) {
35877
+ return fallbacks.get(issueId);
35878
+ }
35879
+ function subscribeFullTextFallback(listener) {
35880
+ listeners$1.add(listener);
35881
+ return () => {
35882
+ listeners$1.delete(listener);
35883
+ };
35884
+ }
35885
+ const HIDES_EXTERNAL_PAYLOAD = { description_hides_external: true };
35886
+ const FULL_TEXT = [];
35887
+ function sameBasis(a, b) {
35888
+ return a.length === b.length && a.every((markdown, index2) => markdown === b[index2]);
35889
+ }
35890
+ function isUnrecoverableRefresh(error) {
35891
+ const status = error == null ? void 0 : error.status;
35892
+ return status === 400 || status === 404;
35893
+ }
35894
+ function useDescriptionEditing({
35895
+ isEditing,
35896
+ saveError = null,
35897
+ ...options
35898
+ }) {
35899
+ const base = useDescriptionEditingBase(options);
35900
+ return { ...base, ...base.resolve({ isEditing, saveError }) };
35901
+ }
35902
+ function useDescriptionEditingBase({
35903
+ issueId,
35904
+ description,
35905
+ view,
35906
+ blocked,
35907
+ canEdit,
35908
+ refresh,
35909
+ reload
35910
+ }) {
35911
+ const [refreshFailure, setRefreshFailure] = useState(
35912
+ null
35913
+ );
35914
+ const fallbackSignature = useSyncExternalStore(
35915
+ subscribeFullTextFallback,
35916
+ () => readFullTextFallback(issueId),
35917
+ () => void 0
35918
+ );
35919
+ const [rejectedIssueId, setRejectedIssueId] = useState(null);
35920
+ const [editBasis, setEditBasis] = useState(FULL_TEXT);
35921
+ const [isRefreshing, setIsRefreshing] = useState(false);
35922
+ const stateSignature = descriptionStateSignature(view, description);
35923
+ const fullText = fallbackSignature === stateSignature;
35924
+ const needsRefresh = !!(view == null ? void 0 : view.needsRefresh) && !blocked && !fullText;
35925
+ const pieces = fullText ? null : (view == null ? void 0 : view.pieces) ?? null;
35926
+ const hiddenBlocks = pieces == null ? void 0 : pieces.flatMap((piece) => piece.kind === "hidden" ? [piece.block] : []);
35927
+ const editable = (hiddenBlocks == null ? void 0 : hiddenBlocks.length) ? editableDescription(description ?? "", hiddenBlocks) : null;
35928
+ const hidesExternal = editable !== null;
35929
+ const nothingToEdit = editable === "";
35930
+ const currentBasis = hidesExternal ? (hiddenBlocks ?? []).map((block) => block.markdown) : FULL_TEXT;
35931
+ const viewKey = `${issueId}\0${view ? JSON.stringify([view.serviceType, view.needsRefresh, currentBasis]) : ""}`;
35932
+ const refreshMessage = (refreshFailure == null ? void 0 : refreshFailure.key) === viewKey ? refreshFailure.message : null;
35933
+ async function refreshDescription() {
35934
+ setRefreshFailure(null);
35935
+ setIsRefreshing(true);
35936
+ try {
35937
+ const fresh = await refresh();
35938
+ const freshView = readDescriptionView(fresh.issue_external_mappings, fresh.description);
35939
+ if (freshView == null ? void 0 : freshView.needsRefresh) {
35940
+ markFullTextFallback(issueId, descriptionStateSignature(freshView, fresh.description));
35941
+ }
35942
+ } catch (error) {
35943
+ if (isUnrecoverableRefresh(error)) markFullTextFallback(issueId, stateSignature);
35944
+ setRefreshFailure({
35945
+ message: error instanceof Error && error.message ? error.message : DESCRIPTION_REFRESH_FAILED_MESSAGE,
35946
+ key: viewKey
35947
+ });
35948
+ } finally {
35949
+ setIsRefreshing(false);
35950
+ }
35951
+ }
35952
+ async function handleSaveRejected({ code: code2 }) {
35953
+ if (code2 !== "DESCRIPTION_EXTERNAL_CHANGED" || editBasis.length === 0) return;
35954
+ let fresh;
35955
+ try {
35956
+ fresh = await reload();
35957
+ } catch {
35958
+ return;
35959
+ }
35960
+ if (!fresh || !isSaveRejectionRepeating({ description, basis: editBasis }, fresh)) return;
35961
+ markFullTextFallback(issueId, stateSignature);
35962
+ setRejectedIssueId(issueId);
35963
+ }
35964
+ function resolve({
35965
+ isEditing,
35966
+ saveError = null
35967
+ }) {
35968
+ const basisChanged = isEditing && !sameBasis(editBasis, currentBasis);
35969
+ const saveBlocked = blocked || needsRefresh || basisChanged;
35970
+ const rejectionActive = isEditing && rejectedIssueId === issueId;
35971
+ return {
35972
+ /** 수정 버튼을 보인다. 편집 중이 아닐 때만 뜻이 있다. */
35973
+ canStartEdit: canEdit && !saveBlocked && !nothingToEdit,
35974
+ /** 편집 중이면 편집란에서 실제로 뺀 조각이 지금과 같을 때, 아니면 모두 숨긴 설명을 편집할 수 있는 사용자에게. */
35975
+ showHiddenNotice: !!view && !!pieces && hidesExternal && !blocked && (isEditing ? editBasis.length > 0 && !basisChanged : nothingToEdit && canEdit),
35976
+ basisChanged,
35977
+ /** 숨긴 조각이 바뀌어 저장이 막힌 이유를 알린다. 다시 불러오기 안내와 겹치지 않게 한다. */
35978
+ showBasisChangedNotice: basisChanged && !needsRefresh,
35979
+ /** 저장이 막힌 이유. 설명이 바뀌었거나(changed), 저장이 되풀이 거절돼 전체 글로 돌아갔다(rejected). */
35980
+ basisChangeReason: rejectionActive ? "rejected" : "changed",
35981
+ saveBlocked,
35982
+ /**
35983
+ * 보여 줄 오류. 저장이 되풀이 거절됐으면 화면이 정한 문구가 먼저다. 지난 저장 오류 문구는 "최신
35984
+ * 내용을 불러왔다"고 하고 서버 문구는 이 화면에 없는 버튼을 가리킬 수 있어 둘 다 맞지 않는다.
35985
+ * 기준이 바뀐 뒤에는 지난 저장 오류가 안내와 모순이라 감춘다.
35986
+ */
35987
+ shownError: rejectionActive ? DESCRIPTION_STRIPPED_SAVE_REJECTED_MESSAGE : basisChanged ? null : saveError ?? refreshMessage
35988
+ };
35989
+ }
35990
+ return {
35991
+ needsRefresh,
35992
+ /** 수정 버튼 대신 다시 불러오기 안내를 보인다. */
35993
+ showRefreshNotice: needsRefresh && canEdit,
35994
+ /** 편집을 시작할 때 편집란에 넣을 값. */
35995
+ startValue: editable ?? description ?? "",
35996
+ /** 편집란에서 뺀 조각이 있어 지금 설명에도 숨길 조각이 있다. 옛 보호 요소 안내를 대신한다. */
35997
+ hidesExternal,
35998
+ hiddenKinds: pieces ? collectHiddenKinds(pieces) : [],
35999
+ /** 저장할 때 함께 보낼 필드. 편집 시작 때 숨긴 조각을 뺐을 때만 있다. */
36000
+ extraPayload: editBasis.length > 0 ? HIDES_EXTERNAL_PAYLOAD : void 0,
36001
+ isRefreshing,
36002
+ /** 수정을 시작한다. 지금 편집란에서 뺄 조각을 기준으로 기록한다. */
36003
+ beginEdit() {
36004
+ setEditBasis(currentBasis);
36005
+ setRefreshFailure(null);
36006
+ setRejectedIssueId(null);
36007
+ },
36008
+ /**
36009
+ * 저장이 거절되면 부른다. 표시를 보낸 저장이 외부 변경(DESCRIPTION_EXTERNAL_CHANGED)으로 거절됐고
36010
+ * 이슈를 다시 받아도 설명과 숨긴 조각이 그대로면 그 이슈를 전체 글 편집으로 돌린다.
36011
+ */
36012
+ handleSaveRejected,
36013
+ refreshDescription,
36014
+ resolve
36015
+ };
36016
+ }
36017
+ function errorCodeOf(err) {
36018
+ var _a;
36019
+ const error = (_a = err == null ? void 0 : err.data) == null ? void 0 : _a.error;
36020
+ return typeof error === "object" && error !== null ? error.code : void 0;
36021
+ }
36022
+ const REFRESH_CODES = /* @__PURE__ */ new Set(["DESCRIPTION_SYNC_LOCKED", "DESCRIPTION_EXTERNAL_CHANGED"]);
36023
+ function useIssueFieldUpdate(issueId, onUpdated, syncService) {
36024
+ const [error, setError] = useState(null);
36025
+ const [savingField, setSavingField] = useState(null);
36026
+ const updateField = useCallback(
36027
+ async (field, value, extra, onRejected) => {
36028
+ setError(null);
36029
+ setSavingField(field);
36030
+ try {
36031
+ await issueService.updateIssue(issueId, { ...extra, [field]: value });
36032
+ applyIssueMutationEffects(issueId);
36033
+ onUpdated == null ? void 0 : onUpdated();
36034
+ return true;
36035
+ } catch (err) {
36036
+ const code2 = errorCodeOf(err);
36037
+ setError(
36038
+ descriptionSyncErrorMessage(code2, syncService) ?? (err instanceof Error ? err.message : "이슈 업데이트에 실패했습니다.")
36039
+ );
36040
+ if (code2 && REFRESH_CODES.has(code2)) {
36041
+ applyIssueMutationEffects(issueId);
36042
+ onUpdated == null ? void 0 : onUpdated();
36043
+ }
36044
+ try {
36045
+ await (onRejected == null ? void 0 : onRejected({ code: code2 }));
36046
+ } catch {
36047
+ }
36048
+ return false;
36049
+ } finally {
36050
+ setSavingField((current2) => current2 === field ? null : current2);
36051
+ }
36052
+ },
36053
+ [issueId, onUpdated, syncService]
36054
+ );
36055
+ const clearError = useCallback(() => setError(null), []);
36056
+ return {
36057
+ error,
36058
+ savingField,
36059
+ updateField,
36060
+ clearError
36061
+ };
36062
+ }
34883
36063
  function IssueDescription({
34884
36064
  issueId,
34885
36065
  description,
34886
36066
  onUpdated,
34887
- readOnly = false
36067
+ readOnly = false,
36068
+ syncLock,
36069
+ view,
36070
+ externalUrl,
36071
+ onRefreshDescription,
36072
+ onReloadIssue
34888
36073
  }) {
34889
36074
  const textareaRef = useRef(null);
34890
36075
  const [draftDescription, setDraftDescription] = useState(description);
34891
36076
  const [isEditing, setIsEditing] = useState(false);
34892
- const { error, savingField, updateField } = useIssueFieldUpdate(issueId, onUpdated);
36077
+ const { error, savingField, updateField, clearError } = useIssueFieldUpdate(
36078
+ issueId,
36079
+ onUpdated,
36080
+ (syncLock == null ? void 0 : syncLock.serviceType) ?? (view == null ? void 0 : view.serviceType)
36081
+ );
36082
+ const blocked = !!(syncLock == null ? void 0 : syncLock.blocksEditing);
36083
+ const editing = useDescriptionEditing({
36084
+ issueId,
36085
+ description,
36086
+ view,
36087
+ blocked,
36088
+ canEdit: !readOnly,
36089
+ isEditing,
36090
+ saveError: error,
36091
+ refresh: onRefreshDescription,
36092
+ reload: onReloadIssue
36093
+ });
34893
36094
  useEffect(() => {
34894
- setDraftDescription(description);
34895
- }, [description]);
36095
+ if (!isEditing) setDraftDescription(description);
36096
+ }, [description, isEditing]);
34896
36097
  const handleStartEdit = () => {
34897
- setDraftDescription(description);
36098
+ editing.beginEdit();
36099
+ clearError();
36100
+ setDraftDescription(editing.startValue);
34898
36101
  setIsEditing(true);
34899
36102
  };
34900
36103
  const handleCancelEdit = () => {
36104
+ clearError();
34901
36105
  setDraftDescription(description);
34902
36106
  setIsEditing(false);
34903
36107
  };
34904
36108
  const handleSave = async () => {
34905
- const success = await updateField("description", draftDescription.trim());
36109
+ if (readOnly || editing.saveBlocked) {
36110
+ return;
36111
+ }
36112
+ const extra = editing.extraPayload;
36113
+ const success = await updateField(
36114
+ "description",
36115
+ draftDescription.trim(),
36116
+ extra,
36117
+ // 뺀 초안의 저장이 같은 이유로 계속 거절되는지는 거절 코드를 보고 이슈를 다시 받아 정한다.
36118
+ extra ? editing.handleSaveRejected : void 0
36119
+ );
34906
36120
  if (success) {
34907
36121
  setIsEditing(false);
34908
36122
  }
@@ -34919,7 +36133,7 @@ function IssueDescription({
34919
36133
  variant: "secondary",
34920
36134
  size: "sm",
34921
36135
  onClick: handleSave,
34922
- disabled: isSaving,
36136
+ disabled: isSaving || editing.saveBlocked,
34923
36137
  children: isSaving ? "저장 중..." : "저장"
34924
36138
  }
34925
36139
  ),
@@ -34934,7 +36148,7 @@ function IssueDescription({
34934
36148
  children: "취소"
34935
36149
  }
34936
36150
  )
34937
- ] }) : !readOnly ? /* @__PURE__ */ jsx(
36151
+ ] }) : editing.canStartEdit ? /* @__PURE__ */ jsx(
34938
36152
  Button,
34939
36153
  {
34940
36154
  type: "button",
@@ -34946,20 +36160,40 @@ function IssueDescription({
34946
36160
  }
34947
36161
  ) : null
34948
36162
  ] }),
36163
+ syncLock && (blocked || !editing.hidesExternal && !editing.showRefreshNotice) ? /* @__PURE__ */ jsx(DescriptionSyncLockNotice, { lock: syncLock, canEdit: !readOnly }) : null,
36164
+ editing.showRefreshNotice ? /* @__PURE__ */ jsx(
36165
+ DescriptionRefreshNotice,
36166
+ {
36167
+ onRefresh: () => void editing.refreshDescription(),
36168
+ isRefreshing: editing.isRefreshing
36169
+ }
36170
+ ) : null,
36171
+ view && editing.showHiddenNotice ? /* @__PURE__ */ jsx(
36172
+ HiddenDescriptionNotice,
36173
+ {
36174
+ serviceType: view.serviceType,
36175
+ kinds: editing.hiddenKinds,
36176
+ externalUrl
36177
+ }
36178
+ ) : null,
36179
+ editing.showBasisChangedNotice ? /* @__PURE__ */ jsx(DescriptionEditBasisChangedNotice, { reason: editing.basisChangeReason }) : null,
34949
36180
  isEditing ? /* @__PURE__ */ jsx(
34950
36181
  Textarea,
34951
36182
  {
34952
36183
  ref: textareaRef,
34953
36184
  value: draftDescription,
34954
- onChange: (event) => setDraftDescription(event.target.value),
36185
+ onChange: (event) => {
36186
+ setDraftDescription(event.target.value);
36187
+ clearError();
36188
+ },
34955
36189
  placeholder: "설명을 입력하세요",
34956
36190
  rows: 6,
34957
36191
  disabled: isSaving,
34958
36192
  className: "min-h-[140px] resize-none overflow-y-auto",
34959
36193
  "aria-label": "설명 입력"
34960
36194
  }
34961
- ) : description ? /* @__PURE__ */ jsx("div", { className: "text-sm leading-relaxed text-text-primary", children: /* @__PURE__ */ jsx(Markdown, { components: markdownComponents, remarkPlugins: markdownRemarkPlugins, children: description }) }) : /* @__PURE__ */ jsx("p", { className: "text-sm text-text-tertiary", children: "설명이 아직 없습니다." }),
34962
- error ? /* @__PURE__ */ jsx("p", { className: "text-xs text-danger", children: error }) : null
36195
+ ) : description ? /* @__PURE__ */ jsx("div", { className: "text-sm leading-relaxed text-text-primary", children: /* @__PURE__ */ jsx(IssueDescriptionView, { description, view }) }) : /* @__PURE__ */ jsx("p", { className: "text-sm text-text-tertiary", children: "설명이 아직 없습니다." }),
36196
+ editing.shownError ? /* @__PURE__ */ jsx("p", { role: "alert", className: "text-xs text-danger", children: editing.shownError }) : null
34963
36197
  ] });
34964
36198
  }
34965
36199
  function getServiceDisplayName(serviceType) {
@@ -35227,18 +36461,20 @@ function IssueHeader({
35227
36461
  const [draftTitle, setDraftTitle] = useState(title);
35228
36462
  const [isEditing, setIsEditing] = useState(false);
35229
36463
  const [validationError, setValidationError] = useState(null);
35230
- const { error, savingField, updateField } = useIssueFieldUpdate(issueId, onUpdated);
36464
+ const { error, savingField, updateField, clearError } = useIssueFieldUpdate(issueId, onUpdated);
35231
36465
  useEffect(() => {
35232
36466
  setDraftTitle(title);
35233
36467
  }, [title]);
35234
36468
  const handleStartEdit = () => {
35235
36469
  setDraftTitle(title);
35236
36470
  setValidationError(null);
36471
+ clearError();
35237
36472
  setIsEditing(true);
35238
36473
  };
35239
36474
  const handleCancelEdit = () => {
35240
36475
  setDraftTitle(title);
35241
36476
  setValidationError(null);
36477
+ clearError();
35242
36478
  setIsEditing(false);
35243
36479
  };
35244
36480
  const handleSave = async () => {
@@ -35267,6 +36503,7 @@ function IssueHeader({
35267
36503
  value: draftTitle,
35268
36504
  onChange: (event) => {
35269
36505
  setDraftTitle(event.target.value);
36506
+ clearError();
35270
36507
  if (validationError) {
35271
36508
  setValidationError(null);
35272
36509
  }
@@ -35354,6 +36591,10 @@ function ImageViewer({
35354
36591
  title = "이미지 전체화면 보기"
35355
36592
  }) {
35356
36593
  const portalContainer = usePortalContainer();
36594
+ const dismissRef = useShadowOutsideDismiss(
36595
+ useCallback(() => onOpenChange(false), [onOpenChange]),
36596
+ { focusOutside: false }
36597
+ );
35357
36598
  return /* @__PURE__ */ jsx(Dialog$1, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogPortal$1, { container: portalContainer, children: [
35358
36599
  /* @__PURE__ */ jsx(
35359
36600
  DialogOverlay$1,
@@ -35368,6 +36609,7 @@ function ImageViewer({
35368
36609
  /* @__PURE__ */ jsxs(
35369
36610
  DialogContent$1,
35370
36611
  {
36612
+ ref: dismissRef,
35371
36613
  onEscapeKeyDown: stopEscapePropagation(),
35372
36614
  style: {
35373
36615
  left: `calc(50% - (var(${BODY_SCROLLBAR_WIDTH_CSS_VAR}, 0px) / 2))`
@@ -35405,6 +36647,11 @@ function ImageViewer({
35405
36647
  )
35406
36648
  ] }) });
35407
36649
  }
36650
+ function orderScreenshotsForDisplay(urls) {
36651
+ if (urls.length !== 2) return [...urls];
36652
+ const [full, element2] = urls;
36653
+ return [element2, full];
36654
+ }
35408
36655
  function parseScreenshotUrls(screenshot) {
35409
36656
  if (Array.isArray(screenshot)) {
35410
36657
  return screenshot.filter((url) => typeof url === "string" && url.length > 0);
@@ -35427,7 +36674,7 @@ function parseScreenshotUrls(screenshot) {
35427
36674
  return [screenshot];
35428
36675
  }
35429
36676
  function IssueScreenshot({ screenshot }) {
35430
- const screenshotUrls = parseScreenshotUrls(screenshot);
36677
+ const screenshotUrls = orderScreenshotsForDisplay(parseScreenshotUrls(screenshot));
35431
36678
  const [selectedImageUrl, setSelectedImageUrl] = useState(
35432
36679
  screenshotUrls[0] ?? null
35433
36680
  );
@@ -35989,6 +37236,7 @@ function getOmittedReasonLabel(reason) {
35989
37236
  if (reason === "transport-budget") return "전송 용량 예산 초과";
35990
37237
  if (reason === "unavailable") return "수집 불가";
35991
37238
  if (reason === "mime-type") return "본문을 저장하지 않는 형식";
37239
+ if (reason === "masking-policy") return MASKING_COPY.maskedLabel;
35992
37240
  return reason;
35993
37241
  }
35994
37242
  function OmittedBodyInfo({
@@ -36188,7 +37436,7 @@ function BodyTab({
36188
37436
  if (omittedBody) {
36189
37437
  return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
36190
37438
  /* @__PURE__ */ jsx(OmittedBodyInfo, { bodyType, ...omittedBody }),
36191
- bodyType === "response" && log && isImageMimeType(getResponseMimeType(log)) && /* @__PURE__ */ jsx(RemoteImagePreview, { url: log.url })
37439
+ bodyType === "response" && log && omittedBody.reason !== "masking-policy" && isImageMimeType(getResponseMimeType(log)) && /* @__PURE__ */ jsx(RemoteImagePreview, { url: log.url })
36192
37440
  ] });
36193
37441
  }
36194
37442
  return /* @__PURE__ */ jsx("div", { className: "text-sm text-text-tertiary", children: emptyMessage });
@@ -36530,22 +37778,26 @@ function canWriteIssueContent(isMember, externalUserId) {
36530
37778
  return Boolean(externalUserId);
36531
37779
  }
36532
37780
  function IssuePopover({ issueId, onClose, onUpdate }) {
36533
- var _a;
37781
+ var _a, _b2;
36534
37782
  const [isOpen, setIsOpen] = useState(true);
36535
37783
  const [activeLogSection, setActiveLogSection] = useState(null);
36536
37784
  const isLogDetailActive = activeLogSection !== null;
36537
37785
  const handleNetworkDetailActiveChange = useCallback((active) => {
36538
- setActiveLogSection((current) => active ? "network" : current === "network" ? null : current);
37786
+ setActiveLogSection((current2) => active ? "network" : current2 === "network" ? null : current2);
36539
37787
  }, []);
36540
37788
  const handleConsoleDetailActiveChange = useCallback((active) => {
36541
- setActiveLogSection((current) => active ? "console" : current === "console" ? null : current);
37789
+ setActiveLogSection((current2) => active ? "console" : current2 === "console" ? null : current2);
36542
37790
  }, []);
36543
37791
  const handleClose = () => setIsOpen(false);
36544
37792
  const { isMember } = useAuthContext();
36545
37793
  const canWriteContent = canWriteIssueContent(isMember, (_a = SDKManager.getInstance().config.user) == null ? void 0 : _a.id);
36546
37794
  const { selectedProjectId } = useProject();
37795
+ const issueQueryKey = useMemo(
37796
+ () => ["issue", issueId, selectedProjectId],
37797
+ [issueId, selectedProjectId]
37798
+ );
36547
37799
  const { data: issue, refetch } = useSuspenseQuery({
36548
- queryKey: ["issue", issueId, selectedProjectId],
37800
+ queryKey: issueQueryKey,
36549
37801
  queryFn: async () => {
36550
37802
  const issueData = await issueService.getIssue(issueId);
36551
37803
  return issueData;
@@ -36568,6 +37820,13 @@ function IssuePopover({ issueId, onClose, onUpdate }) {
36568
37820
  const hasLinkedJiraIssue = Boolean(isMember && (mapping == null ? void 0 : mapping.integration_id) && linkedJiraIssueKey);
36569
37821
  const { isSyncingAll, syncAllFromJira, canSync } = useJiraIssueSync(issue);
36570
37822
  const notionMapping = getPrimaryNotionMapping(issue);
37823
+ const syncLock = readDescriptionSyncLock(issue.issue_external_mappings);
37824
+ const descriptionView = readDescriptionView(issue.issue_external_mappings, issue.description);
37825
+ const { refresh: refreshDescription, reload: reloadIssue } = useRefreshIssueDescription(
37826
+ issue.id,
37827
+ issueQueryKey,
37828
+ onUpdate
37829
+ );
36571
37830
  const showNotionInfo = Boolean(isMember && (notionMapping == null ? void 0 : notionMapping.integration_id));
36572
37831
  return createPortal(
36573
37832
  /* @__PURE__ */ jsxs(
@@ -36598,7 +37857,16 @@ function IssuePopover({ issueId, onClose, onUpdate }) {
36598
37857
  issueId: issue.id,
36599
37858
  description: issue.description ?? "",
36600
37859
  onUpdated: onUpdate,
36601
- readOnly: !canWriteContent
37860
+ readOnly: !canWriteContent,
37861
+ syncLock,
37862
+ view: descriptionView,
37863
+ externalUrl: isMember ? descriptionExternalUrl(
37864
+ issue,
37865
+ descriptionView == null ? void 0 : descriptionView.serviceType,
37866
+ (_b2 = jiraService.getConfig()) == null ? void 0 : _b2.siteUrl
37867
+ ) : null,
37868
+ onRefreshDescription: refreshDescription,
37869
+ onReloadIssue: reloadIssue
36602
37870
  }
36603
37871
  ),
36604
37872
  showJiraInfo && mapping ? /* @__PURE__ */ jsx(
@@ -36801,16 +38069,16 @@ function isVisible(element2) {
36801
38069
  function isRendered(node2) {
36802
38070
  if (!isVisible(node2)) return false;
36803
38071
  const view = node2.ownerDocument.defaultView;
36804
- let current = node2;
36805
- for (let depth = 0; current && depth < HIDDEN_ANCESTOR_LIMIT; depth += 1) {
36806
- if (current.hasAttribute("hidden") || current.getAttribute("aria-hidden") === "true") {
38072
+ let current2 = node2;
38073
+ for (let depth = 0; current2 && depth < HIDDEN_ANCESTOR_LIMIT; depth += 1) {
38074
+ if (current2.hasAttribute("hidden") || current2.getAttribute("aria-hidden") === "true") {
36807
38075
  return false;
36808
38076
  }
36809
- if (current.localName === "dialog" && !current.hasAttribute("open")) return false;
36810
- const style = view == null ? void 0 : view.getComputedStyle(current);
38077
+ if (current2.localName === "dialog" && !current2.hasAttribute("open")) return false;
38078
+ const style = view == null ? void 0 : view.getComputedStyle(current2);
36811
38079
  if ((style == null ? void 0 : style.display) === "none") return false;
36812
- if (current === node2 && (style == null ? void 0 : style.visibility) === "hidden") return false;
36813
- current = current.parentElement;
38080
+ if (current2 === node2 && (style == null ? void 0 : style.visibility) === "hidden") return false;
38081
+ current2 = current2.parentElement;
36814
38082
  }
36815
38083
  return true;
36816
38084
  }
@@ -37675,7 +38943,7 @@ function useDocumentLayoutVersion(enabled, mode) {
37675
38943
  let frame2 = 0;
37676
38944
  const bump = () => {
37677
38945
  frame2 = 0;
37678
- setVersion((current) => current + 1);
38946
+ setVersion((current2) => current2 + 1);
37679
38947
  };
37680
38948
  const scheduleFrame = () => {
37681
38949
  if (frame2) return;
@@ -37774,7 +39042,7 @@ function IssueStickersContainer$1({
37774
39042
  useEffect(() => {
37775
39043
  if (retryAt === void 0) return void 0;
37776
39044
  const timer = window.setTimeout(
37777
- () => setRetryVersion((current) => current + 1),
39045
+ () => setRetryVersion((current2) => current2 + 1),
37778
39046
  Math.max(0, retryAt - Date.now()) + 1
37779
39047
  );
37780
39048
  return () => window.clearTimeout(timer);
@@ -37839,6 +39107,7 @@ function IssueStickersContainer$1({
37839
39107
  [clearClosingCluster, collapseExpandedCluster, expandedClusterId]
37840
39108
  );
37841
39109
  useEffect(() => {
39110
+ var _a;
37842
39111
  if (!expandedClusterId) return void 0;
37843
39112
  const handlePointerDown = (event) => {
37844
39113
  const root2 = rootRef.current;
@@ -37847,8 +39116,9 @@ function IssueStickersContainer$1({
37847
39116
  collapseExpandedCluster();
37848
39117
  }
37849
39118
  };
37850
- document.addEventListener("pointerdown", handlePointerDown, true);
37851
- return () => document.removeEventListener("pointerdown", handlePointerDown, true);
39119
+ const scopes = /* @__PURE__ */ new Set([document, ((_a = rootRef.current) == null ? void 0 : _a.getRootNode()) ?? document]);
39120
+ scopes.forEach((scope) => scope.addEventListener("pointerdown", handlePointerDown, true));
39121
+ return () => scopes.forEach((scope) => scope.removeEventListener("pointerdown", handlePointerDown, true));
37852
39122
  }, [collapseExpandedCluster, expandedClusterId]);
37853
39123
  if (!visible) {
37854
39124
  return null;
@@ -38112,6 +39382,9 @@ function useKeyboardShortcuts(isCreatingIssueRef, onToggleIssueMode, onToggleRec
38112
39382
  return;
38113
39383
  }
38114
39384
  if (isRecordingShortcut(eventLike)) {
39385
+ if (isIssueFormOpen) {
39386
+ return;
39387
+ }
38115
39388
  e.preventDefault();
38116
39389
  e.stopPropagation();
38117
39390
  if (isProcessingRecordingRef.current) {
@@ -38276,7 +39549,7 @@ function useContentAppState() {
38276
39549
  showToast
38277
39550
  } = useIssueMode();
38278
39551
  const screenRecording = useScreenRecording();
38279
- const { clickPosition, clearClickPosition } = useClickPosition(
39552
+ const { clickPosition, clearClickPosition, recaptureScreenshots } = useClickPosition(
38280
39553
  isCreatingIssueRef,
38281
39554
  screenRecording.recordedFile
38282
39555
  );
@@ -38438,16 +39711,27 @@ function useContentAppState() {
38438
39711
  }
38439
39712
  }, [isCreatingIssue]);
38440
39713
  const handleIssueCreated = useCallback(
38441
- async (hasIntegrationFailure = false) => {
39714
+ async (hasIntegrationFailure = false, outcome) => {
39715
+ const hadVideo = Boolean(clickPosition == null ? void 0 : clickPosition.videoFile) && !(outcome == null ? void 0 : outcome.videoExcluded);
39716
+ const policyNotice = maskingCompletionNotice(outcome);
38442
39717
  clearClickPosition();
38443
39718
  screenRecording.clearRecordedFile();
38444
39719
  await loadIssues();
38445
39720
  await toggleIssueMode(false);
38446
39721
  if (!hasIntegrationFailure) {
38447
- showToast("이슈가 생성되었습니다.");
39722
+ const message = hadVideo ? VIDEO_UPLOAD_COPY.created : "이슈가 생성되었습니다.";
39723
+ if (policyNotice) {
39724
+ toast.success(`${message} ${policyNotice}`, {
39725
+ duration: MASKING_NOTICE_TOAST_DURATION_MS
39726
+ });
39727
+ } else {
39728
+ showToast(message);
39729
+ }
39730
+ } else if (policyNotice) {
39731
+ toast.warning(policyNotice, { duration: MASKING_NOTICE_TOAST_DURATION_MS });
38448
39732
  }
38449
39733
  },
38450
- [clearClickPosition, loadIssues, screenRecording, toggleIssueMode, showToast]
39734
+ [clickPosition, clearClickPosition, loadIssues, screenRecording, toggleIssueMode, showToast]
38451
39735
  );
38452
39736
  return {
38453
39737
  // 상태
@@ -38468,6 +39752,7 @@ function useContentAppState() {
38468
39752
  handleIssueCreated,
38469
39753
  loadIssues,
38470
39754
  clearClickPosition,
39755
+ recaptureScreenshots,
38471
39756
  toggleIssueMode,
38472
39757
  handleToggleRecording,
38473
39758
  screenRecordingClearRecordedFile: screenRecording.clearRecordedFile,
@@ -38476,6 +39761,31 @@ function useContentAppState() {
38476
39761
  setFieldFilters
38477
39762
  };
38478
39763
  }
39764
+ function useWidgetVideoUploads(projectId) {
39765
+ const [states, setStates] = useState([]);
39766
+ useEffect(() => getWidgetVideoUploadQueue().subscribe(setStates), []);
39767
+ useEffect(() => {
39768
+ if (!projectId) return;
39769
+ void resumeWidgetIssueUploads(projectId).then((count) => {
39770
+ if (count > 0) toast.info(VIDEO_UPLOAD_COPY.resumed(count));
39771
+ void getWidgetVideoUploadQueue().pruneExpired();
39772
+ void getWidgetAttachmentUploadQueue().pruneExpired();
39773
+ });
39774
+ }, [projectId]);
39775
+ useEffect(() => {
39776
+ const handleBeforeUnload = (event) => {
39777
+ if (getWidgetVideoUploadQueue().hasActiveUploads() || getWidgetAttachmentUploadQueue().hasActiveUploads()) {
39778
+ event.preventDefault();
39779
+ }
39780
+ };
39781
+ window.addEventListener("beforeunload", handleBeforeUnload);
39782
+ return () => window.removeEventListener("beforeunload", handleBeforeUnload);
39783
+ }, []);
39784
+ const retryFailedUploads = () => {
39785
+ states.filter((state) => state.status === "failed").forEach((state) => retryWidgetIssueUploads(state.issueId));
39786
+ };
39787
+ return { uploadProgress: summarizeVideoUploads(states), retryFailedUploads };
39788
+ }
38479
39789
  const STORAGE_KEY = "issue-sticker-sticker-render-mode";
38480
39790
  const QUERY_ROOT = ["stickerRenderMode"];
38481
39791
  function readLocalRenderMode() {
@@ -38841,7 +40151,7 @@ function MoreMenu({
38841
40151
  },
38842
40152
  {
38843
40153
  id: "unrendered",
38844
- label: unrenderedStickerCount > 0 ? `미표시 스티커 ${unrenderedStickerCount}개` : "미표시 스티커 없음",
40154
+ label: unrenderedStickerCount > 0 ? `표시하지 못한 스티커 ${unrenderedStickerCount}개` : "표시하지 못한 스티커 없음",
38845
40155
  icon: /* @__PURE__ */ jsx(CircleAlert, { size: ICON_SIZE }),
38846
40156
  onClick: onOpenUnrenderedStickers,
38847
40157
  // 0개일 때도 노출한다. 숨기면 목록을 열어 확인할 진입점 자체가 없어진다.
@@ -39086,6 +40396,35 @@ const TOOLBAR_STYLES = `
39086
40396
  border: 2px solid hsl(var(--surface-1));
39087
40397
  pointer-events: none;
39088
40398
  }
40399
+ .toolbar-upload {
40400
+ width: 34px;
40401
+ height: 34px;
40402
+ display: flex;
40403
+ align-items: center;
40404
+ justify-content: center;
40405
+ flex: none;
40406
+ border-radius: 50%;
40407
+ border: none;
40408
+ background: transparent;
40409
+ padding: 0;
40410
+ outline: none;
40411
+ }
40412
+ button.toolbar-upload { cursor: pointer; }
40413
+ .toolbar-upload:focus-visible { box-shadow: var(--shadow-focus); }
40414
+ .toolbar-upload svg { transform: rotate(-90deg); }
40415
+ .toolbar-upload-track {
40416
+ fill: none;
40417
+ stroke: hsl(var(--border-subtle));
40418
+ stroke-width: 2.5;
40419
+ }
40420
+ .toolbar-upload-bar {
40421
+ fill: none;
40422
+ stroke: var(--brand);
40423
+ stroke-width: 2.5;
40424
+ stroke-linecap: round;
40425
+ transition: stroke-dashoffset 0.3s ease;
40426
+ }
40427
+ .toolbar-upload[data-failed="true"] .toolbar-upload-bar { stroke: hsl(var(--danger)); }
39089
40428
 
39090
40429
  .toolbar-menu {
39091
40430
  position: absolute;
@@ -39363,6 +40702,13 @@ function formatElapsed(startedAt, now2) {
39363
40702
  const ss = String(seconds % 60).padStart(2, "0");
39364
40703
  return `${mm}:${ss}`;
39365
40704
  }
40705
+ const UPLOAD_RING_RADIUS = 7;
40706
+ const UPLOAD_RING_LENGTH = 2 * Math.PI * UPLOAD_RING_RADIUS;
40707
+ function formatUploadLabel({ percent: percent2, count, failed }) {
40708
+ if (failed) return "영상 업로드 실패";
40709
+ const waiting = count > 1 ? ` · 대기 ${count - 1}개` : "";
40710
+ return `영상 업로드 중 ${percent2}%${waiting}`;
40711
+ }
39366
40712
  function Toolbar({
39367
40713
  onCreateIssue,
39368
40714
  onToggleStickers,
@@ -39384,7 +40730,9 @@ function Toolbar({
39384
40730
  targets,
39385
40731
  account,
39386
40732
  blocked,
39387
- filter: filter2
40733
+ filter: filter2,
40734
+ uploadProgress,
40735
+ onUploadRetry
39388
40736
  }) {
39389
40737
  const rootRef = useRef(null);
39390
40738
  const [internalOpen, setInternalOpen] = useState(false);
@@ -39480,14 +40828,28 @@ function Toolbar({
39480
40828
  ((_a = rootRef.current) == null ? void 0 : _a.querySelectorAll(".toolbar-btn")) ?? []
39481
40829
  );
39482
40830
  if (buttons.length === 0) return;
39483
- const current = buttons.indexOf(document.activeElement);
40831
+ const current2 = buttons.indexOf(document.activeElement);
39484
40832
  const delta = e.key === "ArrowRight" ? 1 : -1;
39485
- const next = (current + delta + buttons.length) % buttons.length;
40833
+ const next = (current2 + delta + buttons.length) % buttons.length;
39486
40834
  (_b2 = buttons[next]) == null ? void 0 : _b2.focus();
39487
40835
  e.preventDefault();
39488
40836
  }, []);
39489
40837
  const anchor = toCssAnchor(placement);
39490
40838
  const menuPlacement = getMenuPlacement(placement);
40839
+ const uploadRing = uploadProgress ? /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 18 18", "aria-hidden": "true", children: [
40840
+ /* @__PURE__ */ jsx("circle", { className: "toolbar-upload-track", cx: "9", cy: "9", r: UPLOAD_RING_RADIUS }),
40841
+ /* @__PURE__ */ jsx(
40842
+ "circle",
40843
+ {
40844
+ className: "toolbar-upload-bar",
40845
+ cx: "9",
40846
+ cy: "9",
40847
+ r: UPLOAD_RING_RADIUS,
40848
+ strokeDasharray: UPLOAD_RING_LENGTH,
40849
+ strokeDashoffset: uploadProgress.failed ? 0 : UPLOAD_RING_LENGTH * (1 - uploadProgress.percent / 100)
40850
+ }
40851
+ )
40852
+ ] }) : null;
39491
40853
  const idleBody = /* @__PURE__ */ jsxs(TooltipProvider, { delayDuration: 240, skipDelayDuration: 120, disableHoverableContent: true, children: [
39492
40854
  /* @__PURE__ */ jsxs(Tooltip, { children: [
39493
40855
  /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
@@ -39526,6 +40888,30 @@ function Toolbar({
39526
40888
  /* @__PURE__ */ jsx("span", { className: "block font-normal opacity-70", children: "공유할 화면을 선택하는 창이 열립니다" })
39527
40889
  ] })
39528
40890
  ] }),
40891
+ uploadProgress ? /* @__PURE__ */ jsxs(Tooltip, { children: [
40892
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: uploadProgress.failed && onUploadRetry ? /* @__PURE__ */ jsx(
40893
+ "button",
40894
+ {
40895
+ type: "button",
40896
+ className: "toolbar-upload",
40897
+ "aria-label": ISSUE_UPLOAD_COPY.retry,
40898
+ "data-failed": true,
40899
+ onClick: onUploadRetry,
40900
+ children: uploadRing
40901
+ }
40902
+ ) : /* @__PURE__ */ jsx(
40903
+ "span",
40904
+ {
40905
+ className: "toolbar-upload",
40906
+ role: "status",
40907
+ "aria-label": formatUploadLabel(uploadProgress),
40908
+ "data-failed": uploadProgress.failed,
40909
+ tabIndex: 0,
40910
+ children: uploadRing
40911
+ }
40912
+ ) }),
40913
+ /* @__PURE__ */ jsx(TooltipContent, { side: "top", sideOffset: 10, children: formatUploadLabel(uploadProgress) })
40914
+ ] }) : null,
39529
40915
  /* @__PURE__ */ jsx("div", { className: "toolbar-divider" }),
39530
40916
  /* @__PURE__ */ jsxs(
39531
40917
  "button",
@@ -39577,7 +40963,7 @@ function Toolbar({
39577
40963
  children: /* @__PURE__ */ jsx(X$1, { size: 15 })
39578
40964
  }
39579
40965
  )
39580
- ] }) : state === "writing" ? /* @__PURE__ */ jsx("span", { className: "toolbar-hint", children: "이슈를 작성 중입니다" }) : state === "recording" ? /* @__PURE__ */ jsxs(Fragment, { children: [
40966
+ ] }) : state === "recording" ? /* @__PURE__ */ jsxs(Fragment, { children: [
39581
40967
  /* @__PURE__ */ jsx("span", { className: "toolbar-pulse", "aria-hidden": "true" }),
39582
40968
  /* @__PURE__ */ jsx("span", { className: "toolbar-elapsed", "aria-hidden": "true", children: formatElapsed(recordingStartedAt, now2) }),
39583
40969
  /* @__PURE__ */ jsx("div", { className: "toolbar-divider" }),
@@ -39592,6 +40978,7 @@ function Toolbar({
39592
40978
  }
39593
40979
  )
39594
40980
  ] }) : idleBody;
40981
+ if (state === "writing") return null;
39595
40982
  return /* @__PURE__ */ jsxs(Fragment, { children: [
39596
40983
  /* @__PURE__ */ jsx("style", { children: TOOLBAR_STYLES }),
39597
40984
  /* @__PURE__ */ jsxs(Popover, { open: filterOpen, onOpenChange: setFilterOpen, children: [
@@ -39736,6 +41123,7 @@ function ContentAppWrapper({
39736
41123
  handleIssueCreated,
39737
41124
  loadIssues,
39738
41125
  clearClickPosition,
41126
+ recaptureScreenshots,
39739
41127
  toggleIssueMode,
39740
41128
  handleToggleRecording,
39741
41129
  screenRecordingClearRecordedFile,
@@ -39747,6 +41135,7 @@ function ContentAppWrapper({
39747
41135
  const { renderMode, saveRenderMode } = useStickerRenderMode();
39748
41136
  const { isMember } = useAuthContext();
39749
41137
  const { selectedProjectId } = useProject();
41138
+ const { uploadProgress, retryFailedUploads } = useWidgetVideoUploads(selectedProjectId);
39750
41139
  const filterProjectId = isMember ? selectedProjectId : null;
39751
41140
  const { data: filterGroups = [] } = useQuery({
39752
41141
  queryKey: ["issueFilterGroups", filterProjectId],
@@ -39789,6 +41178,8 @@ function ContentAppWrapper({
39789
41178
  recordingStartedAt: screenRecording.startedAt,
39790
41179
  showStickers,
39791
41180
  toolbar,
41181
+ uploadProgress,
41182
+ onUploadRetry: retryFailedUploads,
39792
41183
  stickerRenderMode: renderMode,
39793
41184
  onToggleStickerRenderMode: () => void saveRenderMode(nextRenderMode),
39794
41185
  unrenderedStickerCount: unrenderedCount,
@@ -39842,7 +41233,9 @@ function ContentAppWrapper({
39842
41233
  }
39843
41234
  },
39844
41235
  activeIntegration,
39845
- isIntegrationLoading
41236
+ isIntegrationLoading,
41237
+ onScreenRulesChanged: recaptureScreenshots,
41238
+ recaptureScreenshots
39846
41239
  }
39847
41240
  ) })
39848
41241
  ]
@@ -39884,12 +41277,13 @@ function AuthGate() {
39884
41277
  ) })
39885
41278
  ] });
39886
41279
  }
39887
- const globalsCSS = `:root,:host{color-scheme:light;--brand-h: 150;--brand-s: 100%;--brand-l: 34%;--brand-hsl: var(--brand-h) var(--brand-s) var(--brand-l);--brand: hsl(var(--brand-hsl));--brand-hover: hsl(var(--brand-h) calc(var(--brand-s) + 3%) calc(var(--brand-l) - 4%));--brand-active: hsl(var(--brand-h) calc(var(--brand-s) + 2%) calc(var(--brand-l) - 8%));--brand-subtle: hsl(var(--brand-h) calc(var(--brand-s) + 6%) calc(var(--brand-l) + 14%) / .14);--brand-border: hsl(var(--brand-h) calc(var(--brand-s) + 8%) calc(var(--brand-l) + 8%) / .34);--brand-fg: hsl(0 0% 100%);--brand-ring: hsl(var(--brand-h) calc(var(--brand-s) + 12%) calc(var(--brand-l) + 10%) / .22);--font-sans: "Pretendard", -apple-system, BlinkMacSystemFont, system-ui, Roboto, "Helvetica Neue", "Segoe UI", "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", sans-serif;--font-display: var(--font-sans);--font-mono: var(--font-sans);--surface-0: 0 0% 99%;--surface-1: 0 0% 100%;--surface-2: 144 8% 96%;--surface-3: 144 6% 93%;--surface-inset: 144 6% 95%;--overlay-backdrop: hsl(0 0% 0% / .24);--overlay-backdrop-strong: hsl(0 0% 0% / .48);--text-primary: 150 10% 12%;--text-secondary: 148 6% 36%;--text-tertiary: 145 5% 42%;--text-disabled: 145 4% 70%;--text-inverse: 0 0% 100%;--border-subtle: 144 8% 91%;--border-default: 144 6% 85%;--border-strong: 144 5% 72%;--border-focus: var(--brand-h) var(--brand-s) var(--brand-l);--success: var(--brand-h) var(--brand-s) var(--brand-l);--success-subtle: var(--brand-h) calc(var(--brand-s) + 6%) calc(var(--brand-l) + 14%) / .14;--success-surface: var(--brand-h) calc(var(--brand-s) + 6%) calc(var(--brand-l) + 14%) / .24;--warning: 34 82% 52%;--warning-subtle: 34 82% 52% / .14;--danger: 3 68% 56%;--danger-subtle: 3 68% 56% / .12;--danger-deep: 3 62% 40%;--info: 202 52% 44%;--info-subtle: 202 52% 44% / .12;--accent-terra: 13 52% 47%;--accent-terra-subtle: 13 52% 47% / .1;--accent-terra-deep: 13 48% 44%;--accent-blue: 213 86% 56%;--accent-blue-subtle: 213 92% 56% / .12;--accent-blue-deep: 216 78% 40%;--accent-blue-border: 213 86% 62% / .36;--accent-cream: 38 52% 88%;--accent-cream-warm: 40 56% 95%;--accent-cream-deep: 38 48% 82%;--accent-highlight: 55 96% 70%;--accent-ink: 32 13% 11%;--radius-xs: 4px;--radius-sm: 8px;--radius-md: 12px;--radius-lg: 16px;--radius-xl: 20px;--radius-2xl: 24px;--radius-pill: 9999px;--shadow-xs: 0 1px 2px 0 hsl(0 0% 0% / .04);--shadow-sm: 0 1px 3px 0 hsl(0 0% 0% / .06), 0 1px 2px -1px hsl(0 0% 0% / .06);--shadow-md: 0 4px 6px -1px hsl(0 0% 0% / .06), 0 2px 4px -2px hsl(0 0% 0% / .04);--shadow-lg: 0 10px 15px -3px hsl(0 0% 0% / .06), 0 4px 6px -4px hsl(0 0% 0% / .04);--shadow-xl: 0 20px 25px -5px hsl(0 0% 0% / .06), 0 8px 10px -6px hsl(0 0% 0% / .04);--shadow-focus: 0 0 0 3px var(--brand-ring);--shadow-inset: inset 0 1px 2px 0 hsl(0 0% 0% / .04);--shadow-brand: 0 2px 8px hsl(var(--brand-h) calc(var(--brand-s) - 60%) calc(var(--brand-l) - 14%) / .24), 0 6px 20px hsl(var(--brand-h) calc(var(--brand-s) - 70%) calc(var(--brand-l) - 19%) / .16);--duration-instant: 90ms;--duration-fast: .16s;--duration-base: .26s;--duration-out: .18s;--duration-slow: .36s;--ease-out: cubic-bezier(.23, 1, .32, 1);--ease-in-out: cubic-bezier(.45, 0, .15, 1);--ease-spring: cubic-bezier(.18, .94, .32, 1.08);--grid-line: 0 0% 92%}.dark,:host(.dark){color-scheme:dark;--brand-l: 34%;--brand-fg: hsl(0 0% 100%);--surface-0: 150 8% 7%;--surface-1: 150 6% 10%;--surface-2: 150 5% 14%;--surface-3: 150 4% 18%;--surface-inset: 150 6% 6%;--overlay-backdrop: hsl(0 0% 0% / .56);--overlay-backdrop-strong: hsl(0 0% 0% / .8);--text-primary: 40 16% 93%;--text-secondary: 36 8% 68%;--text-tertiary: 34 6% 50%;--text-disabled: 30 5% 34%;--text-inverse: 150 8% 10%;--border-subtle: 150 6% 16%;--border-default: 150 5% 22%;--border-strong: 150 4% 34%;--success: var(--brand-h) var(--brand-s) var(--brand-l);--success-subtle: var(--brand-h) calc(var(--brand-s) + 6%) calc(var(--brand-l) + 14%) / .14;--success-surface: var(--brand-h) calc(var(--brand-s) + 6%) calc(var(--brand-l) + 14%) / .24;--warning: 34 82% 62%;--danger: 2 70% 64%;--danger-deep: 2 80% 76%;--info: 202 54% 62%;--accent-terra: 13 56% 62%;--accent-terra-subtle: 13 56% 62% / .14;--accent-terra-deep: 13 52% 56%;--accent-blue: 210 92% 68%;--accent-blue-subtle: 210 92% 68% / .18;--accent-blue-deep: 209 100% 78%;--accent-blue-border: 210 92% 72% / .44;--accent-cream: 38 30% 26%;--accent-cream-warm: 38 22% 18%;--accent-cream-deep: 38 26% 32%;--accent-highlight: 55 96% 70%;--accent-ink: 32 13% 8%;--shadow-xs: 0 1px 2px 0 hsl(0 0% 0% / .2);--shadow-sm: 0 1px 3px 0 hsl(0 0% 0% / .3), 0 1px 2px -1px hsl(0 0% 0% / .24);--shadow-md: 0 4px 6px -1px hsl(0 0% 0% / .34), 0 2px 4px -2px hsl(0 0% 0% / .2);--shadow-lg: 0 10px 15px -3px hsl(0 0% 0% / .38), 0 4px 6px -4px hsl(0 0% 0% / .24);--shadow-xl: 0 20px 25px -5px hsl(0 0% 0% / .44), 0 8px 10px -6px hsl(0 0% 0% / .28);--shadow-inset: inset 0 1px 2px 0 hsl(0 0% 0% / .16);--shadow-brand: 0 2px 8px hsl(var(--brand-h) calc(var(--brand-s) - 60%) calc(var(--brand-l) - 24%) / .44), 0 6px 20px hsl(0 0% 0% / .32);--grid-line: 150 6% 14%}*{border-color:hsl(var(--border-default))}html{font-family:var(--font-sans),ui-sans-serif,system-ui,-apple-system,sans-serif;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;font-feature-settings:"cv11","ss01","ss03";background-color:hsl(var(--surface-0))}body{margin:0;min-height:100vh;color:hsl(var(--text-primary));background-color:hsl(var(--surface-0))}::-moz-selection{background-color:hsl(var(--brand-h) calc(var(--brand-s) + 6%) calc(var(--brand-l) + 16%) / .22);color:hsl(var(--text-primary))}::selection{background-color:hsl(var(--brand-h) calc(var(--brand-s) + 6%) calc(var(--brand-l) + 16%) / .22);color:hsl(var(--text-primary))}code,kbd,samp,pre{font-family:var(--font-mono),ui-monospace,SFMono-Regular,Menlo,monospace;font-feature-settings:"ss01","ss02","cv11"}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:hsl(var(--border-strong) / .52);border-radius:var(--radius-pill);border:2px solid transparent;background-clip:padding-box}::-webkit-scrollbar-thumb:hover{background:hsl(var(--border-strong) / .9);background-clip:padding-box;border:2px solid transparent}.app-canvas,.grid-canvas{background-color:hsl(var(--surface-0))}.onboarding-canvas{word-break:keep-all;overflow-wrap:break-word;--surface-0: var(--brand-h) var(--brand-s) var(--brand-l);--surface-1: 0 0% 100%;--surface-2: var(--brand-h) 62% 28%;--surface-3: var(--brand-h) 60% 25%;--surface-inset: var(--brand-h) 62% 28%;--text-primary: 0 0% 100%;--text-secondary: var(--brand-h) 46% 90%;--text-tertiary: var(--brand-h) 34% 84%;--text-disabled: var(--brand-h) 20% 72%;--border-subtle: var(--brand-h) 44% 52%;--border-default: var(--brand-h) 40% 58%;--border-strong: var(--brand-h) 36% 70%;--border-focus: 0 0% 100%;--brand-hsl: 0 0% 100%;--brand: hsl(var(--brand-hsl));--brand-hover: hsl(var(--brand-h) 24% 94%);--brand-active: hsl(var(--brand-h) 20% 88%);--brand-fg: hsl(var(--brand-h) var(--brand-s) 24%);--brand-ring: hsl(0 0% 100% / .4);--danger: 6 96% 86%;--danger-subtle: 6 96% 86% / .18;--danger-deep: 6 96% 92%;--warning: 42 96% 82%;--warning-subtle: 42 96% 82% / .18}.onboarding-canvas .bg-surface-1,.onboarding-canvas input,.onboarding-canvas textarea,.onboarding-canvas [role=combobox]{--text-primary: 150 10% 12%;--text-secondary: 148 6% 36%;--text-tertiary: 145 5% 42%;--surface-2: 144 8% 96%;--surface-inset: 144 6% 95%;--border-subtle: 144 8% 91%;--border-default: 144 6% 85%;--brand-hsl: var(--brand-h) var(--brand-s) var(--brand-l);--brand: hsl(var(--brand-hsl));--brand-hover: hsl(var(--brand-h) calc(var(--brand-s) + 3%) calc(var(--brand-l) - 4%));--brand-active: hsl(var(--brand-h) calc(var(--brand-s) + 2%) calc(var(--brand-l) - 8%));--brand-fg: hsl(0 0% 100%);--brand-ring: hsl(var(--brand-h) calc(var(--brand-s) + 12%) calc(var(--brand-l) + 10%) / .22);--warning: 34 82% 52%;--warning-subtle: 34 82% 52% / .14;--danger: 3 68% 56%;--danger-subtle: 3 68% 56% / .12;--danger-deep: 3 62% 40%}@keyframes onboarding-step-in{0%{opacity:0;transform:translate(96px)}to{opacity:1;transform:translate(0)}}.onboarding-step-in{animation:onboarding-step-in var(--duration-slow) var(--ease-out) forwards}@keyframes onboarding-enter-up{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.onboarding-enter{position:fixed;top:0;right:0;bottom:0;left:0;overflow-x:hidden;overflow-y:auto;animation:onboarding-enter-up var(--duration-base) var(--ease-out) forwards}body:has(.onboarding-canvas){background-color:hsl(var(--brand-h) var(--brand-s) var(--brand-l))}.panel-shell,.surface-shell{border:1px solid hsl(var(--border-subtle));border-radius:var(--radius-xl);background:hsl(var(--surface-1));box-shadow:var(--shadow-sm)}.panel-shell-muted,.surface-shell-muted{background:hsl(var(--surface-2));box-shadow:var(--shadow-xs)}.panel-shell-subtle{border:1px solid hsl(var(--border-subtle));border-radius:var(--radius-lg);background:hsl(var(--surface-1));box-shadow:var(--shadow-xs)}.panel-shell-elevated{border:1px solid hsl(var(--border-default));border-radius:var(--radius-xl);background:hsl(var(--surface-1));box-shadow:var(--shadow-md)}.utility-chip,.floating-chip{display:inline-flex;align-items:center;gap:8px;border:1px solid hsl(var(--border-default));border-radius:var(--radius-pill);background:hsl(var(--surface-1));box-shadow:var(--shadow-xs)}.hairline{border-bottom:1px solid hsl(var(--border-subtle))}.mono-label{font-family:var(--font-mono),ui-monospace,monospace;font-size:11px;text-transform:uppercase;color:hsl(var(--text-tertiary))}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:var(--font-sans),ui-sans-serif,system-ui,sans-serif;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--font-mono),ui-monospace,SFMono-Regular,Menlo,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root,:host{--font-sans: "Pretendard", -apple-system, BlinkMacSystemFont, system-ui, Roboto, "Helvetica Neue", "Segoe UI", "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", sans-serif;--font-mono: var(--font-sans);--issue-marker-expand-duration: .52s;--surface-0: 0 0% 99%;--surface-1: 0 0% 100%;--surface-2: 144 8% 96%;--surface-3: 144 6% 93%;--surface-inset: 144 6% 95%;--overlay-backdrop: hsl(0 0% 0% / .24);--overlay-backdrop-strong: hsl(0 0% 0% / .48);--text-primary: 150 10% 12%;--text-secondary: 148 6% 36%;--text-tertiary: 145 5% 52%;--text-disabled: 145 4% 70%;--border-subtle: 144 8% 91%;--border-default: 144 6% 85%;--border-strong: 144 5% 72%;--grid-line: 0 0% 92%;--shadow-xs: 0 1px 2px 0 hsl(0 0% 0% / .04);--shadow-sm: 0 1px 3px 0 hsl(0 0% 0% / .06), 0 1px 2px -1px hsl(0 0% 0% / .06);--shadow-md: 0 4px 6px -1px hsl(0 0% 0% / .06), 0 2px 4px -2px hsl(0 0% 0% / .04);--shadow-lg: 0 10px 15px -3px hsl(0 0% 0% / .06), 0 4px 6px -4px hsl(0 0% 0% / .04);--shadow-xl: 0 20px 25px -5px hsl(0 0% 0% / .06), 0 8px 10px -6px hsl(0 0% 0% / .04);--shadow-focus: 0 0 0 4px var(--brand-ring);--shadow-inset: inset 0 1px 2px 0 hsl(0 0% 0% / .04)}*{--tw-border-opacity: 1;border-color:hsl(var(--border-subtle) / var(--tw-border-opacity, 1))}#issue-sticker-shadow-root{all:initial;display:block;background-color:transparent;--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1));font-family:var(--font-sans);line-height:1.5}.\\!container{width:100%!important}.container{width:100%}@media(min-width:640px){.\\!container{max-width:640px!important}.container{max-width:640px}}@media(min-width:768px){.\\!container{max-width:768px!important}.container{max-width:768px}}@media(min-width:1024px){.\\!container{max-width:1024px!important}.container{max-width:1024px}}@media(min-width:1280px){.\\!container{max-width:1280px!important}.container{max-width:1280px}}@media(min-width:1536px){.\\!container{max-width:1536px!important}.container{max-width:1536px}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.\\!visible{visibility:visible!important}.visible{visibility:visible}.invisible{visibility:hidden}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.inset-x-0{left:0;right:0}.inset-x-1{left:4px;right:4px}.inset-y-0{top:0;bottom:0}.-bottom-px{bottom:-1px}.bottom-0{bottom:0}.bottom-4{bottom:16px}.bottom-6{bottom:24px}.left-0{left:0}.left-1\\/2{left:50%}.left-2{left:8px}.left-4{left:16px}.left-\\[50\\%\\]{left:50%}.right-0{right:0}.right-2{right:8px}.right-3{right:12px}.right-4{right:16px}.right-5{right:20px}.right-6{right:24px}.top-0{top:0}.top-1{top:4px}.top-1\\/2{top:50%}.top-2{top:8px}.top-3{top:12px}.top-4{top:16px}.top-5{top:20px}.top-\\[50\\%\\]{top:50%}.top-\\[calc\\(\\(3\\.5rem-1\\.75rem\\)\\/2\\)\\]{top:14px}.top-full{top:100%}.isolate{isolation:isolate}.z-10{z-index:10}.z-20{z-index:20}.z-\\[1000000\\]{z-index:1000000}.z-\\[1000003\\]{z-index:1000003}.z-\\[1000004\\]{z-index:1000004}.z-\\[1000005\\]{z-index:1000005}.z-\\[1000006\\]{z-index:1000006}.z-\\[1000007\\]{z-index:1000007}.z-\\[1000008\\]{z-index:1000008}.z-\\[1000010\\]{z-index:1000010}.z-\\[1000011\\]{z-index:1000011}.z-\\[10001\\]{z-index:10001}.z-\\[999999\\]{z-index:999999}.z-\\[9999\\]{z-index:9999}.m-0{margin:0}.-mx-2{margin-left:-8px;margin-right:-8px}.mx-3{margin-left:12px;margin-right:12px}.my-1{margin-top:4px;margin-bottom:4px}.my-2{margin-top:8px;margin-bottom:8px}.my-2\\.5{margin-top:10px;margin-bottom:10px}.my-3{margin-top:12px;margin-bottom:12px}.my-4{margin-top:16px;margin-bottom:16px}.mb-0\\.5{margin-bottom:2px}.mb-1{margin-bottom:4px}.mb-2{margin-bottom:8px}.mb-3{margin-bottom:12px}.mb-5{margin-bottom:20px}.ml-0\\.5{margin-left:2px}.ml-4{margin-left:16px}.mr-1\\.5{margin-right:6px}.mt-0{margin-top:0}.mt-0\\.5{margin-top:2px}.mt-1{margin-top:4px}.mt-2{margin-top:8px}.mt-3{margin-top:12px}.mt-4{margin-top:16px}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.aspect-square{aspect-ratio:1 / 1}.\\!h-auto{height:auto!important}.h-1\\.5{height:6px}.h-10{height:40px}.h-11{height:44px}.h-2{height:8px}.h-24{height:96px}.h-3{height:12px}.h-3\\.5{height:14px}.h-4{height:16px}.h-5{height:20px}.h-6{height:24px}.h-7{height:28px}.h-8{height:32px}.h-9{height:36px}.h-\\[1\\.4rem\\]{height:22.4px}.h-\\[100vh\\]{height:100vh}.h-\\[18px\\]{height:18px}.h-\\[220px\\]{height:220px}.h-\\[22px\\]{height:22px}.h-\\[30px\\]{height:30px}.h-\\[min\\(36vh\\,320px\\)\\]{height:min(36vh,320px)}.h-\\[min\\(480px\\,calc\\(100vh-64px\\)\\)\\]{height:min(480px,calc(100vh - 64px))}.h-\\[min\\(54vh\\,520px\\)\\]{height:min(54vh,520px)}.h-\\[min\\(560px\\,calc\\(100vh-32px\\)\\)\\]{height:min(560px,calc(100vh - 32px))}.h-\\[var\\(--issue-sticker-size\\)\\]{height:var(--issue-sticker-size)}.h-\\[var\\(--radix-select-trigger-height\\)\\]{height:var(--radix-select-trigger-height)}.h-auto{height:auto}.h-full{height:100%}.h-px{height:1px}.max-h-24{max-height:96px}.max-h-40{max-height:160px}.max-h-56{max-height:224px}.max-h-80{max-height:320px}.max-h-96{max-height:384px}.max-h-\\[150px\\]{max-height:150px}.max-h-\\[220px\\]{max-height:220px}.max-h-\\[320px\\]{max-height:320px}.max-h-\\[420px\\]{max-height:420px}.max-h-\\[90vh\\]{max-height:90vh}.max-h-\\[calc\\(100vh-3rem\\)\\]{max-height:calc(100vh - 48px)}.max-h-\\[min\\(80vh\\,640px\\)\\]{max-height:min(80vh,640px)}.min-h-0{min-height:0px}.min-h-11{min-height:44px}.min-h-\\[108px\\]{min-height:108px}.min-h-\\[120px\\]{min-height:120px}.min-h-\\[140px\\]{min-height:140px}.min-h-\\[220px\\]{min-height:220px}.w-0\\.5{width:2px}.w-1{width:4px}.w-1\\.5{width:6px}.w-1\\/2{width:50%}.w-10{width:40px}.w-12{width:48px}.w-14{width:56px}.w-16{width:64px}.w-2{width:8px}.w-3{width:12px}.w-3\\.5{width:14px}.w-4{width:16px}.w-5{width:20px}.w-6{width:24px}.w-7{width:28px}.w-72{width:288px}.w-8{width:32px}.w-80{width:320px}.w-9{width:36px}.w-\\[1\\.4rem\\]{width:22.4px}.w-\\[18px\\]{width:18px}.w-\\[22\\.5rem\\]{width:360px}.w-\\[22rem\\]{width:352px}.w-\\[30px\\]{width:30px}.w-\\[38px\\]{width:38px}.w-\\[calc\\(100vw-32px\\)\\]{width:calc(100vw - 32px)}.w-\\[min\\(20rem\\,calc\\(100vw_-_1\\.5rem\\)\\)\\]{width:min(320px,calc(100vw - 24px))}.w-\\[min\\(560px\\,calc\\(100vw-32px\\)\\)\\]{width:min(560px,calc(100vw - 32px))}.w-\\[var\\(--radix-popover-trigger-width\\)\\]{width:var(--radix-popover-trigger-width)}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.w-max{width:-moz-max-content;width:max-content}.min-w-0{min-width:0px}.min-w-9{min-width:36px}.min-w-\\[12rem\\]{min-width:192px}.min-w-\\[200px\\]{min-width:200px}.min-w-\\[var\\(--radix-popover-trigger-width\\)\\]{min-width:var(--radix-popover-trigger-width)}.min-w-\\[var\\(--radix-select-trigger-width\\)\\]{min-width:var(--radix-select-trigger-width)}.max-w-\\[10rem\\]{max-width:160px}.max-w-\\[120px\\]{max-width:120px}.max-w-\\[160px\\]{max-width:160px}.max-w-\\[260px\\]{max-width:260px}.max-w-\\[280px\\]{max-width:280px}.max-w-\\[28rem\\]{max-width:448px}.max-w-\\[300px\\]{max-width:300px}.max-w-\\[360px\\]{max-width:360px}.max-w-\\[36rem\\]{max-width:576px}.max-w-\\[400px\\]{max-width:400px}.max-w-\\[420px\\]{max-width:420px}.max-w-\\[428px\\]{max-width:428px}.max-w-\\[480px\\]{max-width:480px}.max-w-\\[62ch\\]{max-width:62ch}.max-w-\\[640px\\]{max-width:640px}.max-w-\\[70ch\\]{max-width:70ch}.max-w-\\[90vw\\]{max-width:90vw}.max-w-\\[960px\\]{max-width:960px}.max-w-\\[calc\\(100vw-24px\\)\\]{max-width:calc(100vw - 24px)}.max-w-\\[var\\(--issue-sticker-size\\)\\]{max-width:var(--issue-sticker-size)}.max-w-full{max-width:100%}.max-w-md{max-width:448px}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}.grow{flex-grow:1}.caption-bottom{caption-side:bottom}.border-collapse{border-collapse:collapse}.-translate-x-1{--tw-translate-x: -4px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-x-1\\/2{--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-1{--tw-translate-x: 4px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-\\[-50\\%\\]{--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-\\[18px\\]{--tw-translate-x: 18px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-\\[2px\\]{--tw-translate-x: 2px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-\\[-50\\%\\]{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes pulse-ring{0%,to{box-shadow:0 0 0 0 var(--brand-ring)}50%{box-shadow:0 0 0 8px transparent}}.animate-pulse-ring{animation:pulse-ring 1.8s var(--ease-in-out) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-col-resize{cursor:col-resize}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-none{resize:none}.resize-y{resize:vertical}.resize{resize:both}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-row-reverse{flex-direction:row-reverse}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.content-center{align-content:center}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0{gap:0px}.gap-0\\.5{gap:2px}.gap-1{gap:4px}.gap-1\\.5{gap:6px}.gap-2{gap:8px}.gap-2\\.5{gap:10px}.gap-3{gap:12px}.gap-3\\.5{gap:14px}.gap-4{gap:16px}.gap-5{gap:20px}.gap-6{gap:24px}.gap-x-5{-moz-column-gap:20px;column-gap:20px}.gap-y-2{row-gap:8px}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(4px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(4px * var(--tw-space-y-reverse))}.space-y-1\\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(6px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(6px * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(8px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(8px * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(12px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(12px * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(16px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(16px * var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(20px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(20px * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(24px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(24px * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-line-subtle>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:hsl(var(--border-subtle) / var(--tw-divide-opacity, 1))}.self-start{align-self:flex-start}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.break-keep{word-break:keep-all}.\\!rounded-none{border-radius:0!important}.rounded{border-radius:4px}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-\\[18px\\]{border-radius:18px}.rounded-\\[20px\\]{border-radius:20px}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-none{border-radius:0}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-xs{border-radius:var(--radius-xs)}.rounded-b-md{border-bottom-right-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.rounded-t-md{border-top-left-radius:var(--radius-md);border-top-right-radius:var(--radius-md)}.border{border-width:1px}.border-0{border-width:0px}.border-2{border-width:2px}.border-y{border-top-width:1px;border-bottom-width:1px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l{border-left-width:1px}.border-l-2{border-left-width:2px}.border-l-4{border-left-width:4px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-t-0{border-top-width:0px}.border-dashed{border-style:dashed}.border-\\[hsl\\(144_8\\%_91\\%\\)\\]{--tw-border-opacity: 1;border-color:hsl(144 8% 91% / var(--tw-border-opacity, 1))}.border-brand{--tw-border-opacity: 1;border-color:hsl(var(--brand-hsl) / var(--tw-border-opacity, 1))}.border-brand-border{border-color:var(--brand-border)}.border-brand\\/30{border-color:hsl(var(--brand-hsl) / .3)}.border-brand\\/40{border-color:hsl(var(--brand-hsl) / .4)}.border-danger\\/20{border-color:hsl(var(--danger) / .2)}.border-danger\\/30{border-color:hsl(var(--danger) / .3)}.border-info\\/20{border-color:hsl(var(--info) / .2)}.border-info\\/30{border-color:hsl(var(--info) / .3)}.border-line{--tw-border-opacity: 1;border-color:hsl(var(--border-default) / var(--tw-border-opacity, 1))}.border-line-focus{--tw-border-opacity: 1;border-color:hsl(var(--border-focus) / var(--tw-border-opacity, 1))}.border-line-strong{--tw-border-opacity: 1;border-color:hsl(var(--border-strong) / var(--tw-border-opacity, 1))}.border-line-subtle{--tw-border-opacity: 1;border-color:hsl(var(--border-subtle) / var(--tw-border-opacity, 1))}.border-line-subtle\\/50{border-color:hsl(var(--border-subtle) / .5)}.border-line-subtle\\/90{border-color:hsl(var(--border-subtle) / .9)}.border-slate-100{--tw-border-opacity: 1;border-color:rgb(241 245 249 / var(--tw-border-opacity, 1))}.border-slate-200{--tw-border-opacity: 1;border-color:rgb(226 232 240 / var(--tw-border-opacity, 1))}.border-success{--tw-border-opacity: 1;border-color:hsl(var(--success) / var(--tw-border-opacity, 1))}.border-success\\/20{border-color:hsl(var(--success) / .2)}.border-success\\/30{border-color:hsl(var(--success) / .3)}.border-text-primary{--tw-border-opacity: 1;border-color:hsl(var(--text-primary) / var(--tw-border-opacity, 1))}.border-transparent{border-color:transparent}.border-warning\\/20{border-color:hsl(var(--warning) / .2)}.border-warning\\/30{border-color:hsl(var(--warning) / .3)}.border-white\\/70{border-color:#ffffffb3}.border-white\\/80{border-color:#fffc}.border-white\\/90{border-color:#ffffffe6}.border-l-brand{--tw-border-opacity: 1;border-left-color:hsl(var(--brand-hsl) / var(--tw-border-opacity, 1))}.\\!bg-success-surface{background-color:hsl(var(--success-surface))!important}.bg-\\[var\\(--overlay-backdrop-strong\\)\\]{background-color:var(--overlay-backdrop-strong)}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.bg-black\\/10{background-color:#0000001a}.bg-black\\/40{background-color:#0006}.bg-black\\/80{background-color:#000c}.bg-brand{--tw-bg-opacity: 1;background-color:hsl(var(--brand-hsl) / var(--tw-bg-opacity, 1))}.bg-brand-subtle{background-color:var(--brand-subtle)}.bg-brand\\/10{background-color:hsl(var(--brand-hsl) / .1)}.bg-brand\\/5{background-color:hsl(var(--brand-hsl) / .05)}.bg-danger{--tw-bg-opacity: 1;background-color:hsl(var(--danger) / var(--tw-bg-opacity, 1))}.bg-danger-subtle{background-color:hsl(var(--danger-subtle))}.bg-danger\\/10{background-color:hsl(var(--danger) / .1)}.bg-info-subtle{background-color:hsl(var(--info-subtle))}.bg-line{--tw-bg-opacity: 1;background-color:hsl(var(--border-default) / var(--tw-bg-opacity, 1))}.bg-line-subtle{--tw-bg-opacity: 1;background-color:hsl(var(--border-subtle) / var(--tw-bg-opacity, 1))}.bg-success{--tw-bg-opacity: 1;background-color:hsl(var(--success) / var(--tw-bg-opacity, 1))}.bg-success-subtle{background-color:hsl(var(--success-subtle))}.bg-success-surface{background-color:hsl(var(--success-surface))}.bg-surface-0{--tw-bg-opacity: 1;background-color:hsl(var(--surface-0) / var(--tw-bg-opacity, 1))}.bg-surface-1{--tw-bg-opacity: 1;background-color:hsl(var(--surface-1) / var(--tw-bg-opacity, 1))}.bg-surface-2{--tw-bg-opacity: 1;background-color:hsl(var(--surface-2) / var(--tw-bg-opacity, 1))}.bg-surface-3{--tw-bg-opacity: 1;background-color:hsl(var(--surface-3) / var(--tw-bg-opacity, 1))}.bg-surface-inset{--tw-bg-opacity: 1;background-color:hsl(var(--surface-inset) / var(--tw-bg-opacity, 1))}.bg-text-primary{--tw-bg-opacity: 1;background-color:hsl(var(--text-primary) / var(--tw-bg-opacity, 1))}.bg-text-tertiary{--tw-bg-opacity: 1;background-color:hsl(var(--text-tertiary) / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.bg-warning{--tw-bg-opacity: 1;background-color:hsl(var(--warning) / var(--tw-bg-opacity, 1))}.bg-warning-subtle{background-color:hsl(var(--warning-subtle))}.bg-warning\\/10{background-color:hsl(var(--warning) / .1)}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-white\\/10{background-color:#ffffff1a}.bg-white\\/25{background-color:#ffffff40}.fill-text-primary{fill:hsl(var(--text-primary) / 1)}.stroke-\\[3\\]{stroke-width:3}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.\\!p-0{padding:0!important}.p-0{padding:0}.p-0\\.5{padding:2px}.p-1{padding:4px}.p-1\\.5{padding:6px}.p-2{padding:8px}.p-3{padding:12px}.p-3\\.5{padding:14px}.p-4{padding:16px}.p-5{padding:20px}.p-6{padding:24px}.p-7{padding:28px}.p-8{padding:32px}.p-\\[18px\\]{padding:18px}.px-0{padding-left:0;padding-right:0}.px-1\\.5{padding-left:6px;padding-right:6px}.px-2{padding-left:8px;padding-right:8px}.px-2\\.5{padding-left:10px;padding-right:10px}.px-3{padding-left:12px;padding-right:12px}.px-4{padding-left:16px;padding-right:16px}.px-5{padding-left:20px;padding-right:20px}.px-6{padding-left:24px;padding-right:24px}.px-9{padding-left:36px;padding-right:36px}.py-0\\.5{padding-top:2px;padding-bottom:2px}.py-1{padding-top:4px;padding-bottom:4px}.py-1\\.5{padding-top:6px;padding-bottom:6px}.py-10{padding-top:40px;padding-bottom:40px}.py-16{padding-top:64px;padding-bottom:64px}.py-2{padding-top:8px;padding-bottom:8px}.py-2\\.5{padding-top:10px;padding-bottom:10px}.py-3{padding-top:12px;padding-bottom:12px}.py-3\\.5{padding-top:14px;padding-bottom:14px}.py-4{padding-top:16px;padding-bottom:16px}.py-5{padding-top:20px;padding-bottom:20px}.py-6{padding-top:24px;padding-bottom:24px}.py-8{padding-top:32px;padding-bottom:32px}.pb-1{padding-bottom:4px}.pb-3{padding-bottom:12px}.pb-4{padding-bottom:16px}.pl-3{padding-left:12px}.pl-4{padding-left:16px}.pl-5{padding-left:20px}.pl-8{padding-left:32px}.pr-10{padding-right:40px}.pr-3{padding-right:12px}.pr-4{padding-right:16px}.pt-0{padding-top:0}.pt-1{padding-top:4px}.pt-2\\.5{padding-top:10px}.pt-4{padding-top:16px}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.align-middle{vertical-align:middle}.font-display{font-family:var(--font-display),ui-sans-serif,system-ui,sans-serif}.font-mono{font-family:var(--font-mono),ui-monospace,SFMono-Regular,Menlo,monospace}.font-sans{font-family:var(--font-sans),ui-sans-serif,system-ui,sans-serif}.text-2xs{font-size:clamp(10.5px,10.19px + .082vw,11.5px);line-height:1.333}.text-\\[0\\.72rem\\]{font-size:11.52px}.text-\\[1\\.45rem\\]{font-size:23.2px}.text-\\[1\\.75rem\\]{font-size:28px}.text-\\[10px\\]{font-size:10px}.text-\\[11px\\]{font-size:11px}.text-base{font-size:clamp(14px,13.39px + .163vw,16px);line-height:1.5}.text-lg{font-size:clamp(17px,16.39px + .163vw,19px);line-height:1.471}.text-sm{font-size:clamp(13px,12.54px + .122vw,14.5px);line-height:1.462}.text-xl{font-size:clamp(20px,19.08px + .245vw,23px);line-height:1.45}.text-xs{font-size:clamp(12px,11.69px + .082vw,13px);line-height:1.333}.font-black{font-weight:900}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing: tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-4{line-height:16px}.leading-5{line-height:20px}.leading-8{line-height:32px}.leading-\\[1\\.55\\]{line-height:1.55}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-tight{line-height:1.25}.text-\\[hsl\\(145_5\\%_42\\%\\)\\]{--tw-text-opacity: 1;color:hsl(145 5% 42% / var(--tw-text-opacity, 1))}.text-brand{--tw-text-opacity: 1;color:hsl(var(--brand-hsl) / var(--tw-text-opacity, 1))}.text-brand-fg{color:var(--brand-fg)}.text-brand\\/50{color:hsl(var(--brand-hsl) / .5)}.text-danger{--tw-text-opacity: 1;color:hsl(var(--danger) / var(--tw-text-opacity, 1))}.text-danger\\/80{color:hsl(var(--danger) / .8)}.text-info{--tw-text-opacity: 1;color:hsl(var(--info) / var(--tw-text-opacity, 1))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.text-slate-700{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity, 1))}.text-slate-900{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity, 1))}.text-success{--tw-text-opacity: 1;color:hsl(var(--success) / var(--tw-text-opacity, 1))}.text-text-disabled{--tw-text-opacity: 1;color:hsl(var(--text-disabled) / var(--tw-text-opacity, 1))}.text-text-inverse{--tw-text-opacity: 1;color:hsl(var(--text-inverse) / var(--tw-text-opacity, 1))}.text-text-primary{--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1))}.text-text-secondary{--tw-text-opacity: 1;color:hsl(var(--text-secondary) / var(--tw-text-opacity, 1))}.text-text-tertiary{--tw-text-opacity: 1;color:hsl(var(--text-tertiary) / var(--tw-text-opacity, 1))}.text-transparent{color:transparent}.text-warning{--tw-text-opacity: 1;color:hsl(var(--warning) / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-white\\/90{color:#ffffffe6}.underline{text-decoration-line:underline}.line-through{text-decoration-line:line-through}.underline-offset-2{text-underline-offset:2px}.underline-offset-4{text-underline-offset:4px}.accent-brand{accent-color:hsl(var(--brand-hsl) / 1)}.opacity-0{opacity:0}.opacity-100{opacity:1}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.opacity-80{opacity:.8}.\\!shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1) !important;--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color) !important;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)!important}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\\[0_24px_70px_-28px_rgba\\(16\\,24\\,18\\,0\\.45\\)\\]{--tw-shadow: 0 24px 70px -28px rgba(16,24,18,.45);--tw-shadow-colored: 0 24px 70px -28px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: var(--shadow-lg);--tw-shadow-colored: var(--shadow-lg);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: var(--shadow-md);--tw-shadow-colored: var(--shadow-md);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: var(--shadow-sm);--tw-shadow-colored: var(--shadow-sm);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: var(--shadow-xl);--tw-shadow-colored: var(--shadow-xl);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xs{--tw-shadow: var(--shadow-xs);--tw-shadow-colored: var(--shadow-xs);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.outline-2{outline-width:2px}.outline-offset-0{outline-offset:0px}.outline-transparent{outline-color:transparent}.ring{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-2{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-4{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-brand{--tw-ring-opacity: 1;--tw-ring-color: hsl(var(--brand-hsl) / var(--tw-ring-opacity, 1))}.ring-brand-subtle{--tw-ring-color: var(--brand-subtle)}.blur{--tw-blur: blur(8px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-\\[2px\\]{--tw-backdrop-blur: blur(2px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\\[background-color\\,border-color\\,color\\,box-shadow\\]{transition-property:background-color,border-color,color,box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\\[background-color\\,color\\,border-color\\,box-shadow\\,opacity\\]{transition-property:background-color,color,border-color,box-shadow,opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\\[background-color\\,color\\,border-color\\,box-shadow\\]{transition-property:background-color,color,border-color,box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\\[background-color\\,color\\,box-shadow\\]{transition-property:background-color,color,box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\\[background-color\\,color\\]{transition-property:background-color,color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\\[border-color\\,box-shadow\\,background-color\\]{transition-property:border-color,box-shadow,background-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\\[border-color\\,outline-color\\,box-shadow\\,background-color\\,transform\\]{transition-property:border-color,outline-color,box-shadow,background-color,transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\\[max-width\\,height\\,box-shadow\\,background-color\\,border-color\\,transform\\,opacity\\]{transition-property:max-width,height,box-shadow,background-color,border-color,transform,opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\\[max-width\\]{transition-property:max-width;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\\[opacity\\,transform\\]{transition-property:opacity,transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}.duration-\\[var\\(--issue-marker-expand-duration\\)\\]{transition-duration:var(--issue-marker-expand-duration)}.duration-fast{transition-duration:var(--duration-fast)}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-in-out{transition-timing-function:var(--ease-in-out)}.ease-out{transition-timing-function:var(--ease-out)}@keyframes float-gentle{0%,to{transform:translateY(0)}50%{transform:translateY(-2px)}}@keyframes toast-slide{0%{opacity:0;transform:translateY(-14px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes gradient-shift{0%,to{background-position:0% 50%}50%{background-position:100% 50%}}.issue-overlay-shell{border:1px solid hsl(var(--border-subtle));border-radius:var(--radius-lg);background:hsl(var(--surface-1));box-shadow:var(--shadow-md)}.issue-marker-breathe{animation:float-gentle 4.8s var(--ease-in-out) infinite}.issue-marker-breathe:hover,.issue-marker-breathe:focus-visible,.issue-marker-breathe:focus-within{animation-play-state:paused}.\\[overflow-wrap\\:anywhere\\]{overflow-wrap:anywhere}.marker\\:text-text-tertiary *::marker{color:hsl(var(--text-tertiary))}.marker\\:text-text-tertiary::marker{color:hsl(var(--text-tertiary))}.file\\:border-0::file-selector-button{border-width:0px}.file\\:bg-transparent::file-selector-button{background-color:transparent}.file\\:text-sm::file-selector-button{font-size:clamp(13px,12.54px + .122vw,14.5px);line-height:1.462}.file\\:font-medium::file-selector-button{font-weight:500}.file\\:text-text-primary::file-selector-button{--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1))}.placeholder\\:text-text-tertiary::-moz-placeholder{--tw-text-opacity: 1;color:hsl(var(--text-tertiary) / var(--tw-text-opacity, 1))}.placeholder\\:text-text-tertiary::placeholder{--tw-text-opacity: 1;color:hsl(var(--text-tertiary) / var(--tw-text-opacity, 1))}.first\\:mt-0:first-child{margin-top:0}.first\\:border-t-0:first-child{border-top-width:0px}.first\\:pt-0:first-child{padding-top:0}.focus-within\\:z-\\[10001\\]:focus-within{z-index:10001}.focus-within\\:border-line-focus:focus-within{--tw-border-opacity: 1;border-color:hsl(var(--border-focus) / var(--tw-border-opacity, 1))}.hover\\:z-\\[10001\\]:hover{z-index:10001}.hover\\:border-line:hover{--tw-border-opacity: 1;border-color:hsl(var(--border-default) / var(--tw-border-opacity, 1))}.hover\\:border-line-strong:hover{--tw-border-opacity: 1;border-color:hsl(var(--border-strong) / var(--tw-border-opacity, 1))}.hover\\:border-line-subtle:hover{--tw-border-opacity: 1;border-color:hsl(var(--border-subtle) / var(--tw-border-opacity, 1))}.hover\\:bg-\\[hsl\\(144_8\\%_96\\%\\)\\]:hover{--tw-bg-opacity: 1;background-color:hsl(144 8% 96% / var(--tw-bg-opacity, 1))}.hover\\:bg-blue-500:hover{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.hover\\:bg-brand-hover:hover{background-color:var(--brand-hover)}.hover\\:bg-brand\\/40:hover{background-color:hsl(var(--brand-hsl) / .4)}.hover\\:bg-danger-subtle:hover{background-color:hsl(var(--danger-subtle))}.hover\\:bg-danger\\/10:hover{background-color:hsl(var(--danger) / .1)}.hover\\:bg-slate-100:hover{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity, 1))}.hover\\:bg-slate-50:hover{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity, 1))}.hover\\:bg-surface-2:hover{--tw-bg-opacity: 1;background-color:hsl(var(--surface-2) / var(--tw-bg-opacity, 1))}.hover\\:bg-surface-2\\/60:hover{background-color:hsl(var(--surface-2) / .6)}.hover\\:bg-surface-3:hover{--tw-bg-opacity: 1;background-color:hsl(var(--surface-3) / var(--tw-bg-opacity, 1))}.hover\\:bg-surface-inset:hover{--tw-bg-opacity: 1;background-color:hsl(var(--surface-inset) / var(--tw-bg-opacity, 1))}.hover\\:bg-transparent:hover{background-color:transparent}.hover\\:bg-white\\/20:hover{background-color:#fff3}.hover\\:text-\\[hsl\\(150_10\\%_12\\%\\)\\]:hover{--tw-text-opacity: 1;color:hsl(150 10% 12% / var(--tw-text-opacity, 1))}.hover\\:text-brand-hover:hover{color:var(--brand-hover)}.hover\\:text-danger:hover{--tw-text-opacity: 1;color:hsl(var(--danger) / var(--tw-text-opacity, 1))}.hover\\:text-slate-900:hover{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity, 1))}.hover\\:text-text-primary:hover{--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1))}.hover\\:text-text-secondary:hover{--tw-text-opacity: 1;color:hsl(var(--text-secondary) / var(--tw-text-opacity, 1))}.hover\\:underline:hover{text-decoration-line:underline}.hover\\:opacity-90:hover{opacity:.9}.focus\\:border-brand:focus{--tw-border-opacity: 1;border-color:hsl(var(--brand-hsl) / var(--tw-border-opacity, 1))}.focus\\:bg-surface-2:focus{--tw-bg-opacity: 1;background-color:hsl(var(--surface-2) / var(--tw-bg-opacity, 1))}.focus\\:text-text-primary:focus{--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1))}.focus\\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus-visible\\:h-\\[var\\(--issue-sticker-hover-size\\)\\]:focus-visible{height:var(--issue-sticker-hover-size)}.focus-visible\\:max-w-\\[160px\\]:focus-visible{max-width:160px}.focus-visible\\:max-w-\\[480px\\]:focus-visible{max-width:480px}.focus-visible\\:border-line-focus:focus-visible{--tw-border-opacity: 1;border-color:hsl(var(--border-focus) / var(--tw-border-opacity, 1))}.focus-visible\\:border-transparent:focus-visible{border-color:transparent}.focus-visible\\:bg-slate-50:focus-visible{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity, 1))}.focus-visible\\:bg-surface-1:focus-visible{--tw-bg-opacity: 1;background-color:hsl(var(--surface-1) / var(--tw-bg-opacity, 1))}.focus-visible\\:bg-surface-2:focus-visible{--tw-bg-opacity: 1;background-color:hsl(var(--surface-2) / var(--tw-bg-opacity, 1))}.focus-visible\\:text-text-primary:focus-visible{--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1))}.focus-visible\\:shadow-focus:focus-visible{--tw-shadow: var(--shadow-focus);--tw-shadow-colored: var(--shadow-focus);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.focus-visible\\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\\:outline-line-focus:focus-visible{outline-color:hsl(var(--border-focus) / 1)}.focus-visible\\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\\:ring-4:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\\:ring-brand-subtle:focus-visible{--tw-ring-color: var(--brand-subtle)}.focus-visible\\:ring-white\\/40:focus-visible{--tw-ring-color: rgb(255 255 255 / .4)}.active\\:bg-brand-active:active{background-color:var(--brand-active)}.active\\:bg-brand\\/60:active{background-color:hsl(var(--brand-hsl) / .6)}.disabled\\:pointer-events-none:disabled{pointer-events:none}.disabled\\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\\:opacity-40:disabled{opacity:.4}.disabled\\:opacity-50:disabled{opacity:.5}.group\\/marker:hover .group-hover\\/marker\\:h-\\[var\\(--issue-sticker-hover-size\\)\\]{height:var(--issue-sticker-hover-size)}.group\\/marker:hover .group-hover\\/marker\\:max-w-\\[160px\\]{max-width:160px}.group\\/marker:hover .group-hover\\/marker\\:max-w-\\[480px\\]{max-width:480px}.group\\/marker:hover .group-hover\\/marker\\:translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\\/marker:hover .group-hover\\/marker\\:bg-brand-hover{background-color:var(--brand-hover)}.group\\/marker:hover .group-hover\\/marker\\:opacity-100{opacity:1}.group\\/marker:hover .group-hover\\/marker\\:shadow-xl{--tw-shadow: var(--shadow-xl);--tw-shadow-colored: var(--shadow-xl);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.group:focus-visible .group-focus-visible\\:translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:focus-visible .group-focus-visible\\:opacity-100{opacity:1}.group.toaster .group-\\[\\.toaster\\]\\:isolate{isolation:isolate}.group.toast .group-\\[\\.toast\\]\\:rounded-md,.group.toaster .group-\\[\\.toaster\\]\\:rounded-md{border-radius:var(--radius-md)}.group.toaster .group-\\[\\.toaster\\]\\:border{border-width:1px}.group.toaster .group-\\[\\.toaster\\]\\:\\!border-danger\\/30{border-color:hsl(var(--danger) / .3)!important}.group.toaster .group-\\[\\.toaster\\]\\:\\!border-info\\/30{border-color:hsl(var(--info) / .3)!important}.group.toaster .group-\\[\\.toaster\\]\\:\\!border-success\\/30{border-color:hsl(var(--success) / .3)!important}.group.toaster .group-\\[\\.toaster\\]\\:\\!border-warning\\/30{border-color:hsl(var(--warning) / .3)!important}.group.toaster .group-\\[\\.toaster\\]\\:border-line-subtle{--tw-border-opacity: 1;border-color:hsl(var(--border-subtle) / var(--tw-border-opacity, 1))}.group.toast .group-\\[\\.toast\\]\\:bg-brand{--tw-bg-opacity: 1;background-color:hsl(var(--brand-hsl) / var(--tw-bg-opacity, 1))}.group.toast .group-\\[\\.toast\\]\\:bg-surface-2{--tw-bg-opacity: 1;background-color:hsl(var(--surface-2) / var(--tw-bg-opacity, 1))}.group.toaster .group-\\[\\.toaster\\]\\:\\!bg-danger-subtle{background-color:hsl(var(--danger-subtle))!important}.group.toaster .group-\\[\\.toaster\\]\\:\\!bg-info-subtle{background-color:hsl(var(--info-subtle))!important}.group.toaster .group-\\[\\.toaster\\]\\:\\!bg-success-surface{background-color:hsl(var(--success-surface))!important}.group.toaster .group-\\[\\.toaster\\]\\:\\!bg-warning-subtle{background-color:hsl(var(--warning-subtle))!important}.group.toaster .group-\\[\\.toaster\\]\\:bg-surface-1{--tw-bg-opacity: 1;background-color:hsl(var(--surface-1) / var(--tw-bg-opacity, 1))}.group.toaster .group-\\[\\.toaster\\]\\:px-4{padding-left:16px;padding-right:16px}.group.toaster .group-\\[\\.toaster\\]\\:py-3{padding-top:12px;padding-bottom:12px}.group.toast .group-\\[\\.toast\\]\\:text-sm{font-size:clamp(13px,12.54px + .122vw,14.5px);line-height:1.462}.group.toast .group-\\[\\.toast\\]\\:text-xs{font-size:clamp(12px,11.69px + .082vw,13px);line-height:1.333}.group.toast .group-\\[\\.toast\\]\\:font-medium{font-weight:500}.group.toast .group-\\[\\.toast\\]\\:text-brand-fg{color:var(--brand-fg)}.group.toast .group-\\[\\.toast\\]\\:text-text-primary{--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1))}.group.toast .group-\\[\\.toast\\]\\:text-text-secondary{--tw-text-opacity: 1;color:hsl(var(--text-secondary) / var(--tw-text-opacity, 1))}.group.toaster .group-\\[\\.toaster\\]\\:text-text-primary{--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1))}.group.toaster .group-\\[\\.toaster\\]\\:shadow-sm{--tw-shadow: var(--shadow-sm);--tw-shadow-colored: var(--shadow-sm);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.group.toaster .group-\\[\\.toaster\\]\\:before\\:absolute:before{content:var(--tw-content);position:absolute}.group.toaster .group-\\[\\.toaster\\]\\:before\\:inset-0:before{content:var(--tw-content);top:0;right:0;bottom:0;left:0}.group.toaster .group-\\[\\.toaster\\]\\:before\\:-z-10:before{content:var(--tw-content);z-index:-10}.group.toaster .group-\\[\\.toaster\\]\\:before\\:rounded-md:before{content:var(--tw-content);border-radius:var(--radius-md)}.group.toaster .group-\\[\\.toaster\\]\\:before\\:bg-surface-1:before{content:var(--tw-content);--tw-bg-opacity: 1;background-color:hsl(var(--surface-1) / var(--tw-bg-opacity, 1))}.group.toaster .group-\\[\\.toaster\\]\\:before\\:content-\\[\\'\\'\\]:before{--tw-content: "";content:var(--tw-content)}.peer:focus-visible~.peer-focus-visible\\:shadow-focus{--tw-shadow: var(--shadow-focus);--tw-shadow-colored: var(--shadow-focus);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.peer:disabled~.peer-disabled\\:cursor-not-allowed{cursor:not-allowed}.peer:disabled~.peer-disabled\\:opacity-60{opacity:.6}.aria-disabled\\:pointer-events-none[aria-disabled=true]{pointer-events:none}.aria-disabled\\:opacity-40[aria-disabled=true]{opacity:.4}.data-\\[disabled\\]\\:pointer-events-none[data-disabled]{pointer-events:none}@keyframes dialog-out{0%{opacity:1;transform:translate(-50%,-50%) scale(1)}to{opacity:0;transform:translate(-50%,calc(-50% + 8px)) scale(.985)}}.data-\\[state\\=closed\\]\\:animate-dialog-out[data-state=closed]{animation:dialog-out var(--duration-out) var(--ease-out) forwards}@keyframes fade-out{0%{opacity:1}to{opacity:0}}.data-\\[state\\=closed\\]\\:animate-fade-out[data-state=closed]{animation:fade-out var(--duration-out) var(--ease-out) forwards}@keyframes scale-out{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.97)}}.data-\\[state\\=closed\\]\\:animate-scale-out[data-state=closed]{animation:scale-out var(--duration-out) var(--ease-out) forwards}@keyframes sheet-out-left{0%{transform:translate(0)}to{transform:translate(-100%);opacity:.6}}.data-\\[state\\=closed\\]\\:animate-sheet-out-left[data-state=closed]{animation:sheet-out-left var(--duration-base) var(--ease-out) forwards}@keyframes sheet-out-right{0%{transform:translate(0);opacity:1}to{transform:translate(100%);opacity:.6}}.data-\\[state\\=closed\\]\\:animate-sheet-out-right[data-state=closed]{animation:sheet-out-right var(--duration-base) var(--ease-out) forwards}.data-\\[state\\=delayed-open\\]\\:animate-scale-in[data-state=delayed-open]{animation:scale-in var(--duration-base) var(--ease-out) forwards}@keyframes dialog-in{0%{opacity:0;transform:translate(-50%,calc(-50% + 12px)) scale(.985)}to{opacity:1;transform:translate(-50%,-50%) scale(1)}}.data-\\[state\\=open\\]\\:animate-dialog-in[data-state=open]{animation:dialog-in var(--duration-base) var(--ease-out) forwards}@keyframes fade-in{0%{opacity:0}to{opacity:1}}.data-\\[state\\=open\\]\\:animate-fade-in[data-state=open]{animation:fade-in var(--duration-base) var(--ease-out) forwards}@keyframes scale-in{0%{opacity:0;transform:scale(.98)}to{opacity:1;transform:scale(1)}}.data-\\[state\\=open\\]\\:animate-scale-in[data-state=open]{animation:scale-in var(--duration-base) var(--ease-out) forwards}@keyframes sheet-in-left{0%{transform:translate(-100%);opacity:.6}to{transform:translate(0);opacity:1}}.data-\\[state\\=open\\]\\:animate-sheet-in-left[data-state=open]{animation:sheet-in-left var(--duration-base) var(--ease-out) forwards}@keyframes sheet-in-right{0%{transform:translate(100%);opacity:.6}to{transform:translate(0)}}.data-\\[state\\=open\\]\\:animate-sheet-in-right[data-state=open]{animation:sheet-in-right var(--duration-base) var(--ease-out) forwards}.data-\\[range-end\\=true\\]\\:rounded-md[data-range-end=true]{border-radius:var(--radius-md)}.data-\\[range-middle\\=true\\]\\:rounded-none[data-range-middle=true]{border-radius:0}.data-\\[range-start\\=true\\]\\:rounded-md[data-range-start=true]{border-radius:var(--radius-md)}.data-\\[state\\=open\\]\\:border-line-focus[data-state=open]{--tw-border-opacity: 1;border-color:hsl(var(--border-focus) / var(--tw-border-opacity, 1))}.data-\\[highlighted\\]\\:bg-surface-2[data-highlighted]{--tw-bg-opacity: 1;background-color:hsl(var(--surface-2) / var(--tw-bg-opacity, 1))}.data-\\[range-end\\=true\\]\\:bg-brand[data-range-end=true]{--tw-bg-opacity: 1;background-color:hsl(var(--brand-hsl) / var(--tw-bg-opacity, 1))}.data-\\[range-middle\\=true\\]\\:bg-transparent[data-range-middle=true]{background-color:transparent}.data-\\[range-start\\=true\\]\\:bg-brand[data-range-start=true],.data-\\[selected-single\\=true\\]\\:bg-brand[data-selected-single=true]{--tw-bg-opacity: 1;background-color:hsl(var(--brand-hsl) / var(--tw-bg-opacity, 1))}.data-\\[state\\=selected\\]\\:bg-brand-subtle[data-state=selected]{background-color:var(--brand-subtle)}.data-\\[today\\=true\\]\\:font-semibold[data-today=true]{font-weight:600}.data-\\[highlighted\\]\\:text-text-primary[data-highlighted]{--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1))}.data-\\[placeholder\\]\\:text-text-tertiary[data-placeholder]{--tw-text-opacity: 1;color:hsl(var(--text-tertiary) / var(--tw-text-opacity, 1))}.data-\\[range-end\\=true\\]\\:text-brand-fg[data-range-end=true]{color:var(--brand-fg)}.data-\\[range-middle\\=true\\]\\:text-text-primary[data-range-middle=true]{--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1))}.data-\\[range-start\\=true\\]\\:text-brand-fg[data-range-start=true],.data-\\[selected-single\\=true\\]\\:text-brand-fg[data-selected-single=true]{color:var(--brand-fg)}.data-\\[state\\=active\\]\\:text-text-primary[data-state=active],.data-\\[state\\=checked\\]\\:text-text-primary[data-state=checked]{--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1))}.data-\\[today\\=true\\]\\:text-brand[data-today=true]{--tw-text-opacity: 1;color:hsl(var(--brand-hsl) / var(--tw-text-opacity, 1))}.data-\\[disabled\\]\\:opacity-50[data-disabled]{opacity:.5}.data-\\[state\\=open\\]\\:shadow-focus[data-state=open]{--tw-shadow: var(--shadow-focus);--tw-shadow-colored: var(--shadow-focus);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.data-\\[range-end\\=true\\]\\:hover\\:bg-brand-hover:hover[data-range-end=true],.data-\\[range-start\\=true\\]\\:hover\\:bg-brand-hover:hover[data-range-start=true],.data-\\[selected-single\\=true\\]\\:hover\\:bg-brand-hover:hover[data-selected-single=true]{background-color:var(--brand-hover)}.data-\\[range-end\\=true\\]\\:hover\\:text-brand-fg:hover[data-range-end=true],.data-\\[range-start\\=true\\]\\:hover\\:text-brand-fg:hover[data-range-start=true],.data-\\[selected-single\\=true\\]\\:hover\\:text-brand-fg:hover[data-selected-single=true]{color:var(--brand-fg)}@media(prefers-reduced-motion:reduce){.motion-reduce\\:animate-none{animation:none}}@media(min-width:640px){.sm\\:flex-row{flex-direction:row}.sm\\:items-start{align-items:flex-start}.sm\\:items-center{align-items:center}.sm\\:justify-end{justify-content:flex-end}.sm\\:justify-between{justify-content:space-between}.sm\\:gap-2\\.5{gap:10px}.sm\\:p-5{padding:20px}.sm\\:p-6{padding:24px}.sm\\:text-2xl{font-size:clamp(26px,24.78px + .327vw,30px);line-height:1.308}.sm\\:text-\\[2\\.1rem\\]{font-size:33.6px}}@media(min-width:768px){.md\\:flex{display:flex}.md\\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\\:grid-cols-\\[minmax\\(0\\,1fr\\)_minmax\\(260px\\,340px\\)\\]{grid-template-columns:minmax(0,1fr) minmax(260px,340px)}}.\\[\\&\\>\\*\\:not\\(summary\\)\\]\\:pl-4>*:not(summary){padding-left:16px}.\\[\\&\\>span\\]\\:line-clamp-1>span{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:1}.\\[\\&\\>tr\\]\\:last\\:border-b-0:last-child>tr{border-bottom-width:0px}.\\[\\&\\[data-state\\=open\\]\\>span\\[data-chevron\\]\\]\\:rotate-180[data-state=open]>span[data-chevron]{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group.toaster .group-\\[\\.toaster\\]\\:\\[\\&_\\[data-icon\\]\\]\\:text-danger [data-icon]{--tw-text-opacity: 1;color:hsl(var(--danger) / var(--tw-text-opacity, 1))}.group.toaster .group-\\[\\.toaster\\]\\:\\[\\&_\\[data-icon\\]\\]\\:text-info [data-icon]{--tw-text-opacity: 1;color:hsl(var(--info) / var(--tw-text-opacity, 1))}.group.toaster .group-\\[\\.toaster\\]\\:\\[\\&_\\[data-icon\\]\\]\\:text-success [data-icon]{--tw-text-opacity: 1;color:hsl(var(--success) / var(--tw-text-opacity, 1))}.group.toaster .group-\\[\\.toaster\\]\\:\\[\\&_\\[data-icon\\]\\]\\:text-warning [data-icon]{--tw-text-opacity: 1;color:hsl(var(--warning) / var(--tw-text-opacity, 1))}.\\[\\&_tr\\]\\:border-b tr{border-bottom-width:1px}.\\[\\&_tr\\]\\:border-line-subtle tr{--tw-border-opacity: 1;border-color:hsl(var(--border-subtle) / var(--tw-border-opacity, 1))}`;
41280
+ const globalsCSS = `:root,:host{color-scheme:light;--brand-h: 150;--brand-s: 100%;--brand-l: 34%;--brand-hsl: var(--brand-h) var(--brand-s) var(--brand-l);--brand: hsl(var(--brand-hsl));--brand-hover: hsl(var(--brand-h) calc(var(--brand-s) + 3%) calc(var(--brand-l) - 4%));--brand-active: hsl(var(--brand-h) calc(var(--brand-s) + 2%) calc(var(--brand-l) - 8%));--brand-subtle: hsl(var(--brand-h) calc(var(--brand-s) + 6%) calc(var(--brand-l) + 14%) / .14);--brand-border: hsl(var(--brand-h) calc(var(--brand-s) + 8%) calc(var(--brand-l) + 8%) / .34);--brand-fg: hsl(0 0% 100%);--brand-ring: hsl(var(--brand-h) calc(var(--brand-s) + 12%) calc(var(--brand-l) + 10%) / .22);--font-sans: "Pretendard", -apple-system, BlinkMacSystemFont, system-ui, Roboto, "Helvetica Neue", "Segoe UI", "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", sans-serif;--font-display: var(--font-sans);--font-mono: var(--font-sans);--surface-0: 0 0% 99%;--surface-1: 0 0% 100%;--surface-2: 144 8% 96%;--surface-3: 144 6% 93%;--surface-inset: 144 6% 95%;--overlay-backdrop: hsl(0 0% 0% / .24);--overlay-backdrop-strong: hsl(0 0% 0% / .48);--text-primary: 150 10% 12%;--text-secondary: 148 6% 36%;--text-tertiary: 145 5% 42%;--text-disabled: 145 4% 70%;--text-inverse: 0 0% 100%;--border-subtle: 144 8% 91%;--border-default: 144 6% 85%;--border-strong: 144 5% 72%;--border-focus: var(--brand-h) var(--brand-s) var(--brand-l);--success: var(--brand-h) var(--brand-s) var(--brand-l);--success-subtle: var(--brand-h) calc(var(--brand-s) + 6%) calc(var(--brand-l) + 14%) / .14;--success-surface: var(--brand-h) calc(var(--brand-s) + 6%) calc(var(--brand-l) + 14%) / .24;--warning: 34 82% 52%;--warning-subtle: 34 82% 52% / .14;--danger: 3 68% 56%;--danger-subtle: 3 68% 56% / .12;--danger-deep: 3 62% 40%;--info: 202 52% 44%;--info-subtle: 202 52% 44% / .12;--accent-terra: 13 52% 47%;--accent-terra-subtle: 13 52% 47% / .1;--accent-terra-deep: 13 48% 44%;--accent-blue: 213 86% 56%;--accent-blue-subtle: 213 92% 56% / .12;--accent-blue-deep: 216 78% 40%;--accent-blue-border: 213 86% 62% / .36;--accent-cream: 38 52% 88%;--accent-cream-warm: 40 56% 95%;--accent-cream-deep: 38 48% 82%;--accent-highlight: 55 96% 70%;--accent-ink: 32 13% 11%;--radius-xs: 4px;--radius-sm: 8px;--radius-md: 12px;--radius-lg: 16px;--radius-xl: 20px;--radius-2xl: 24px;--radius-pill: 9999px;--shadow-xs: 0 1px 2px 0 hsl(0 0% 0% / .04);--shadow-sm: 0 1px 3px 0 hsl(0 0% 0% / .06), 0 1px 2px -1px hsl(0 0% 0% / .06);--shadow-md: 0 4px 6px -1px hsl(0 0% 0% / .06), 0 2px 4px -2px hsl(0 0% 0% / .04);--shadow-lg: 0 10px 15px -3px hsl(0 0% 0% / .06), 0 4px 6px -4px hsl(0 0% 0% / .04);--shadow-xl: 0 20px 25px -5px hsl(0 0% 0% / .06), 0 8px 10px -6px hsl(0 0% 0% / .04);--shadow-focus: 0 0 0 3px var(--brand-ring);--shadow-inset: inset 0 1px 2px 0 hsl(0 0% 0% / .04);--shadow-brand: 0 2px 8px hsl(var(--brand-h) calc(var(--brand-s) - 60%) calc(var(--brand-l) - 14%) / .24), 0 6px 20px hsl(var(--brand-h) calc(var(--brand-s) - 70%) calc(var(--brand-l) - 19%) / .16);--duration-instant: 90ms;--duration-fast: .16s;--duration-base: .26s;--duration-out: .18s;--duration-slow: .36s;--ease-out: cubic-bezier(.23, 1, .32, 1);--ease-in-out: cubic-bezier(.45, 0, .15, 1);--ease-spring: cubic-bezier(.18, .94, .32, 1.08);--grid-line: 0 0% 92%}.dark,:host(.dark){color-scheme:dark;--brand-l: 34%;--brand-fg: hsl(0 0% 100%);--surface-0: 150 8% 7%;--surface-1: 150 6% 10%;--surface-2: 150 5% 14%;--surface-3: 150 4% 18%;--surface-inset: 150 6% 6%;--overlay-backdrop: hsl(0 0% 0% / .56);--overlay-backdrop-strong: hsl(0 0% 0% / .8);--text-primary: 40 16% 93%;--text-secondary: 36 8% 68%;--text-tertiary: 34 6% 50%;--text-disabled: 30 5% 34%;--text-inverse: 150 8% 10%;--border-subtle: 150 6% 16%;--border-default: 150 5% 22%;--border-strong: 150 4% 34%;--success: var(--brand-h) var(--brand-s) var(--brand-l);--success-subtle: var(--brand-h) calc(var(--brand-s) + 6%) calc(var(--brand-l) + 14%) / .14;--success-surface: var(--brand-h) calc(var(--brand-s) + 6%) calc(var(--brand-l) + 14%) / .24;--warning: 34 82% 62%;--danger: 2 70% 64%;--danger-deep: 2 80% 76%;--info: 202 54% 62%;--accent-terra: 13 56% 62%;--accent-terra-subtle: 13 56% 62% / .14;--accent-terra-deep: 13 52% 56%;--accent-blue: 210 92% 68%;--accent-blue-subtle: 210 92% 68% / .18;--accent-blue-deep: 209 100% 78%;--accent-blue-border: 210 92% 72% / .44;--accent-cream: 38 30% 26%;--accent-cream-warm: 38 22% 18%;--accent-cream-deep: 38 26% 32%;--accent-highlight: 55 96% 70%;--accent-ink: 32 13% 8%;--shadow-xs: 0 1px 2px 0 hsl(0 0% 0% / .2);--shadow-sm: 0 1px 3px 0 hsl(0 0% 0% / .3), 0 1px 2px -1px hsl(0 0% 0% / .24);--shadow-md: 0 4px 6px -1px hsl(0 0% 0% / .34), 0 2px 4px -2px hsl(0 0% 0% / .2);--shadow-lg: 0 10px 15px -3px hsl(0 0% 0% / .38), 0 4px 6px -4px hsl(0 0% 0% / .24);--shadow-xl: 0 20px 25px -5px hsl(0 0% 0% / .44), 0 8px 10px -6px hsl(0 0% 0% / .28);--shadow-inset: inset 0 1px 2px 0 hsl(0 0% 0% / .16);--shadow-brand: 0 2px 8px hsl(var(--brand-h) calc(var(--brand-s) - 60%) calc(var(--brand-l) - 24%) / .44), 0 6px 20px hsl(0 0% 0% / .32);--grid-line: 150 6% 14%}*{border-color:hsl(var(--border-default))}html{font-family:var(--font-sans),ui-sans-serif,system-ui,-apple-system,sans-serif;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;font-feature-settings:"cv11","ss01","ss03";background-color:hsl(var(--surface-0))}body{margin:0;min-height:100vh;color:hsl(var(--text-primary));background-color:hsl(var(--surface-0))}::-moz-selection{background-color:hsl(var(--brand-h) calc(var(--brand-s) + 6%) calc(var(--brand-l) + 16%) / .22);color:hsl(var(--text-primary))}::selection{background-color:hsl(var(--brand-h) calc(var(--brand-s) + 6%) calc(var(--brand-l) + 16%) / .22);color:hsl(var(--text-primary))}code,kbd,samp,pre{font-family:var(--font-mono),ui-monospace,SFMono-Regular,Menlo,monospace;font-feature-settings:"ss01","ss02","cv11"}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:hsl(var(--border-strong) / .52);border-radius:var(--radius-pill);border:2px solid transparent;background-clip:padding-box}::-webkit-scrollbar-thumb:hover{background:hsl(var(--border-strong) / .9);background-clip:padding-box;border:2px solid transparent}.app-canvas,.grid-canvas{background-color:hsl(var(--surface-0))}.onboarding-canvas{word-break:keep-all;overflow-wrap:break-word;--surface-0: var(--brand-h) var(--brand-s) var(--brand-l);--surface-1: 0 0% 100%;--surface-2: var(--brand-h) 62% 28%;--surface-3: var(--brand-h) 60% 25%;--surface-inset: var(--brand-h) 62% 28%;--text-primary: 0 0% 100%;--text-secondary: var(--brand-h) 46% 90%;--text-tertiary: var(--brand-h) 34% 84%;--text-disabled: var(--brand-h) 20% 72%;--border-subtle: var(--brand-h) 44% 52%;--border-default: var(--brand-h) 40% 58%;--border-strong: var(--brand-h) 36% 70%;--border-focus: 0 0% 100%;--brand-hsl: 0 0% 100%;--brand: hsl(var(--brand-hsl));--brand-hover: hsl(var(--brand-h) 24% 94%);--brand-active: hsl(var(--brand-h) 20% 88%);--brand-fg: hsl(var(--brand-h) var(--brand-s) 24%);--brand-ring: hsl(0 0% 100% / .4);--danger: 6 96% 86%;--danger-subtle: 6 96% 86% / .18;--danger-deep: 6 96% 92%;--warning: 42 96% 82%;--warning-subtle: 42 96% 82% / .18}.onboarding-canvas .bg-surface-1,.onboarding-canvas input,.onboarding-canvas textarea,.onboarding-canvas [role=combobox]{--text-primary: 150 10% 12%;--text-secondary: 148 6% 36%;--text-tertiary: 145 5% 42%;--surface-2: 144 8% 96%;--surface-inset: 144 6% 95%;--border-subtle: 144 8% 91%;--border-default: 144 6% 85%;--brand-hsl: var(--brand-h) var(--brand-s) var(--brand-l);--brand: hsl(var(--brand-hsl));--brand-hover: hsl(var(--brand-h) calc(var(--brand-s) + 3%) calc(var(--brand-l) - 4%));--brand-active: hsl(var(--brand-h) calc(var(--brand-s) + 2%) calc(var(--brand-l) - 8%));--brand-fg: hsl(0 0% 100%);--brand-ring: hsl(var(--brand-h) calc(var(--brand-s) + 12%) calc(var(--brand-l) + 10%) / .22);--warning: 34 82% 52%;--warning-subtle: 34 82% 52% / .14;--danger: 3 68% 56%;--danger-subtle: 3 68% 56% / .12;--danger-deep: 3 62% 40%}@keyframes onboarding-step-in{0%{opacity:0;transform:translate(96px)}to{opacity:1;transform:translate(0)}}.onboarding-step-in{animation:onboarding-step-in var(--duration-slow) var(--ease-out) forwards}@keyframes onboarding-enter-up{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.onboarding-enter{position:fixed;top:0;right:0;bottom:0;left:0;overflow-x:hidden;overflow-y:auto;animation:onboarding-enter-up var(--duration-base) var(--ease-out) forwards}body:has(.onboarding-canvas){background-color:hsl(var(--brand-h) var(--brand-s) var(--brand-l))}.panel-shell,.surface-shell{border:1px solid hsl(var(--border-subtle));border-radius:var(--radius-xl);background:hsl(var(--surface-1));box-shadow:var(--shadow-sm)}.panel-shell-muted,.surface-shell-muted{background:hsl(var(--surface-2));box-shadow:var(--shadow-xs)}.panel-shell-subtle{border:1px solid hsl(var(--border-subtle));border-radius:var(--radius-lg);background:hsl(var(--surface-1));box-shadow:var(--shadow-xs)}.panel-shell-elevated{border:1px solid hsl(var(--border-default));border-radius:var(--radius-xl);background:hsl(var(--surface-1));box-shadow:var(--shadow-md)}.utility-chip,.floating-chip{display:inline-flex;align-items:center;gap:8px;border:1px solid hsl(var(--border-default));border-radius:var(--radius-pill);background:hsl(var(--surface-1));box-shadow:var(--shadow-xs)}.hairline{border-bottom:1px solid hsl(var(--border-subtle))}.mono-label{font-family:var(--font-mono),ui-monospace,monospace;font-size:11px;text-transform:uppercase;color:hsl(var(--text-tertiary))}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:var(--font-sans),ui-sans-serif,system-ui,sans-serif;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--font-mono),ui-monospace,SFMono-Regular,Menlo,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root,:host{--font-sans: "Pretendard", -apple-system, BlinkMacSystemFont, system-ui, Roboto, "Helvetica Neue", "Segoe UI", "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", sans-serif;--font-mono: var(--font-sans);--issue-marker-expand-duration: .52s;--surface-0: 0 0% 99%;--surface-1: 0 0% 100%;--surface-2: 144 8% 96%;--surface-3: 144 6% 93%;--surface-inset: 144 6% 95%;--overlay-backdrop: hsl(0 0% 0% / .24);--overlay-backdrop-strong: hsl(0 0% 0% / .48);--text-primary: 150 10% 12%;--text-secondary: 148 6% 36%;--text-tertiary: 145 5% 52%;--text-disabled: 145 4% 70%;--border-subtle: 144 8% 91%;--border-default: 144 6% 85%;--border-strong: 144 5% 72%;--grid-line: 0 0% 92%;--shadow-xs: 0 1px 2px 0 hsl(0 0% 0% / .04);--shadow-sm: 0 1px 3px 0 hsl(0 0% 0% / .06), 0 1px 2px -1px hsl(0 0% 0% / .06);--shadow-md: 0 4px 6px -1px hsl(0 0% 0% / .06), 0 2px 4px -2px hsl(0 0% 0% / .04);--shadow-lg: 0 10px 15px -3px hsl(0 0% 0% / .06), 0 4px 6px -4px hsl(0 0% 0% / .04);--shadow-xl: 0 20px 25px -5px hsl(0 0% 0% / .06), 0 8px 10px -6px hsl(0 0% 0% / .04);--shadow-focus: 0 0 0 4px var(--brand-ring);--shadow-inset: inset 0 1px 2px 0 hsl(0 0% 0% / .04)}*{--tw-border-opacity: 1;border-color:hsl(var(--border-subtle) / var(--tw-border-opacity, 1))}#issue-sticker-shadow-root{all:initial;display:block;background-color:transparent;--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1));font-family:var(--font-sans);line-height:1.5}.\\!container{width:100%!important}.container{width:100%}@media(min-width:640px){.\\!container{max-width:640px!important}.container{max-width:640px}}@media(min-width:768px){.\\!container{max-width:768px!important}.container{max-width:768px}}@media(min-width:1024px){.\\!container{max-width:1024px!important}.container{max-width:1024px}}@media(min-width:1280px){.\\!container{max-width:1280px!important}.container{max-width:1280px}}@media(min-width:1536px){.\\!container{max-width:1536px!important}.container{max-width:1536px}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.\\!visible{visibility:visible!important}.visible{visibility:visible}.invisible{visibility:hidden}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.inset-x-0{left:0;right:0}.inset-x-1{left:4px;right:4px}.inset-y-0{top:0;bottom:0}.-bottom-px{bottom:-1px}.bottom-0{bottom:0}.bottom-4{bottom:16px}.bottom-6{bottom:24px}.left-0{left:0}.left-1\\/2{left:50%}.left-2{left:8px}.left-4{left:16px}.left-\\[50\\%\\]{left:50%}.right-0{right:0}.right-2{right:8px}.right-3{right:12px}.right-4{right:16px}.right-5{right:20px}.right-6{right:24px}.top-0{top:0}.top-1{top:4px}.top-1\\/2{top:50%}.top-2{top:8px}.top-3{top:12px}.top-4{top:16px}.top-5{top:20px}.top-\\[50\\%\\]{top:50%}.top-\\[calc\\(\\(3\\.5rem-1\\.75rem\\)\\/2\\)\\]{top:14px}.top-full{top:100%}.isolate{isolation:isolate}.z-10{z-index:10}.z-20{z-index:20}.z-\\[1000000\\]{z-index:1000000}.z-\\[1000003\\]{z-index:1000003}.z-\\[1000004\\]{z-index:1000004}.z-\\[1000005\\]{z-index:1000005}.z-\\[1000006\\]{z-index:1000006}.z-\\[1000007\\]{z-index:1000007}.z-\\[1000008\\]{z-index:1000008}.z-\\[1000010\\]{z-index:1000010}.z-\\[1000011\\]{z-index:1000011}.z-\\[10001\\]{z-index:10001}.z-\\[999999\\]{z-index:999999}.z-\\[9999\\]{z-index:9999}.m-0{margin:0}.-mx-2{margin-left:-8px;margin-right:-8px}.mx-3{margin-left:12px;margin-right:12px}.my-1{margin-top:4px;margin-bottom:4px}.my-2{margin-top:8px;margin-bottom:8px}.my-2\\.5{margin-top:10px;margin-bottom:10px}.my-3{margin-top:12px;margin-bottom:12px}.my-4{margin-top:16px;margin-bottom:16px}.mb-0\\.5{margin-bottom:2px}.mb-1{margin-bottom:4px}.mb-2{margin-bottom:8px}.mb-3{margin-bottom:12px}.mb-5{margin-bottom:20px}.ml-0\\.5{margin-left:2px}.ml-4{margin-left:16px}.mr-1\\.5{margin-right:6px}.mr-2{margin-right:8px}.mt-0{margin-top:0}.mt-0\\.5{margin-top:2px}.mt-1{margin-top:4px}.mt-2{margin-top:8px}.mt-3{margin-top:12px}.mt-4{margin-top:16px}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.inline-table{display:inline-table}.table-caption{display:table-caption}.table-cell{display:table-cell}.table-column{display:table-column}.table-column-group{display:table-column-group}.table-footer-group{display:table-footer-group}.table-header-group{display:table-header-group}.table-row-group{display:table-row-group}.table-row{display:table-row}.flow-root{display:flow-root}.grid{display:grid}.inline-grid{display:inline-grid}.contents{display:contents}.list-item{display:list-item}.\\!hidden{display:none!important}.hidden{display:none}.aspect-square{aspect-ratio:1 / 1}.size-3{width:12px;height:12px}.size-4{width:16px;height:16px}.\\!h-auto{height:auto!important}.h-1\\.5{height:6px}.h-10{height:40px}.h-11{height:44px}.h-2{height:8px}.h-24{height:96px}.h-3{height:12px}.h-3\\.5{height:14px}.h-4{height:16px}.h-5{height:20px}.h-6{height:24px}.h-7{height:28px}.h-8{height:32px}.h-9{height:36px}.h-\\[1\\.4rem\\]{height:22.4px}.h-\\[100vh\\]{height:100vh}.h-\\[18px\\]{height:18px}.h-\\[220px\\]{height:220px}.h-\\[22px\\]{height:22px}.h-\\[30px\\]{height:30px}.h-\\[min\\(36vh\\,320px\\)\\]{height:min(36vh,320px)}.h-\\[min\\(480px\\,calc\\(100vh-64px\\)\\)\\]{height:min(480px,calc(100vh - 64px))}.h-\\[min\\(54vh\\,520px\\)\\]{height:min(54vh,520px)}.h-\\[min\\(560px\\,calc\\(100vh-32px\\)\\)\\]{height:min(560px,calc(100vh - 32px))}.h-\\[var\\(--issue-sticker-size\\)\\]{height:var(--issue-sticker-size)}.h-\\[var\\(--radix-select-trigger-height\\)\\]{height:var(--radix-select-trigger-height)}.h-auto{height:auto}.h-full{height:100%}.h-px{height:1px}.max-h-24{max-height:96px}.max-h-40{max-height:160px}.max-h-56{max-height:224px}.max-h-80{max-height:320px}.max-h-96{max-height:384px}.max-h-\\[150px\\]{max-height:150px}.max-h-\\[220px\\]{max-height:220px}.max-h-\\[320px\\]{max-height:320px}.max-h-\\[420px\\]{max-height:420px}.max-h-\\[90vh\\]{max-height:90vh}.max-h-\\[calc\\(100vh-3rem\\)\\]{max-height:calc(100vh - 48px)}.max-h-\\[min\\(80vh\\,640px\\)\\]{max-height:min(80vh,640px)}.min-h-0{min-height:0px}.min-h-11{min-height:44px}.min-h-\\[108px\\]{min-height:108px}.min-h-\\[120px\\]{min-height:120px}.min-h-\\[140px\\]{min-height:140px}.min-h-\\[220px\\]{min-height:220px}.w-0\\.5{width:2px}.w-1{width:4px}.w-1\\.5{width:6px}.w-1\\/2{width:50%}.w-10{width:40px}.w-12{width:48px}.w-14{width:56px}.w-16{width:64px}.w-2{width:8px}.w-3{width:12px}.w-3\\.5{width:14px}.w-4{width:16px}.w-5{width:20px}.w-6{width:24px}.w-7{width:28px}.w-72{width:288px}.w-8{width:32px}.w-80{width:320px}.w-9{width:36px}.w-\\[1\\.4rem\\]{width:22.4px}.w-\\[18px\\]{width:18px}.w-\\[22\\.5rem\\]{width:360px}.w-\\[22rem\\]{width:352px}.w-\\[30px\\]{width:30px}.w-\\[38px\\]{width:38px}.w-\\[calc\\(100vw-32px\\)\\]{width:calc(100vw - 32px)}.w-\\[min\\(24rem\\,calc\\(100vw_-_1\\.5rem\\)\\)\\]{width:min(384px,calc(100vw - 24px))}.w-\\[min\\(30rem\\,calc\\(100vw_-_1\\.5rem\\)\\)\\]{width:min(480px,calc(100vw - 24px))}.w-\\[min\\(560px\\,calc\\(100vw-32px\\)\\)\\]{width:min(560px,calc(100vw - 32px))}.w-\\[var\\(--radix-popover-trigger-width\\)\\]{width:var(--radix-popover-trigger-width)}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.w-max{width:-moz-max-content;width:max-content}.min-w-0{min-width:0px}.min-w-9{min-width:36px}.min-w-\\[12rem\\]{min-width:192px}.min-w-\\[200px\\]{min-width:200px}.min-w-\\[var\\(--radix-popover-trigger-width\\)\\]{min-width:var(--radix-popover-trigger-width)}.min-w-\\[var\\(--radix-select-trigger-width\\)\\]{min-width:var(--radix-select-trigger-width)}.max-w-\\[10rem\\]{max-width:160px}.max-w-\\[120px\\]{max-width:120px}.max-w-\\[160px\\]{max-width:160px}.max-w-\\[16rem\\]{max-width:256px}.max-w-\\[260px\\]{max-width:260px}.max-w-\\[280px\\]{max-width:280px}.max-w-\\[28rem\\]{max-width:448px}.max-w-\\[300px\\]{max-width:300px}.max-w-\\[360px\\]{max-width:360px}.max-w-\\[36rem\\]{max-width:576px}.max-w-\\[400px\\]{max-width:400px}.max-w-\\[420px\\]{max-width:420px}.max-w-\\[428px\\]{max-width:428px}.max-w-\\[480px\\]{max-width:480px}.max-w-\\[62ch\\]{max-width:62ch}.max-w-\\[640px\\]{max-width:640px}.max-w-\\[70ch\\]{max-width:70ch}.max-w-\\[90vw\\]{max-width:90vw}.max-w-\\[960px\\]{max-width:960px}.max-w-\\[calc\\(100vw-24px\\)\\]{max-width:calc(100vw - 24px)}.max-w-\\[var\\(--issue-sticker-size\\)\\]{max-width:var(--issue-sticker-size)}.max-w-full{max-width:100%}.max-w-md{max-width:448px}.flex-1{flex:1 1 0%}.flex-shrink{flex-shrink:1}.flex-shrink-0,.shrink-0{flex-shrink:0}.flex-grow,.grow{flex-grow:1}.caption-bottom{caption-side:bottom}.border-collapse{border-collapse:collapse}.-translate-x-1{--tw-translate-x: -4px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-x-1\\/2{--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-1{--tw-translate-x: 4px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-\\[-50\\%\\]{--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-\\[18px\\]{--tw-translate-x: 18px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-\\[2px\\]{--tw-translate-x: 2px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-\\[-50\\%\\]{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes pulse-ring{0%,to{box-shadow:0 0 0 0 var(--brand-ring)}50%{box-shadow:0 0 0 8px transparent}}.animate-pulse-ring{animation:pulse-ring 1.8s var(--ease-in-out) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-col-resize{cursor:col-resize}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-none{resize:none}.resize-y{resize:vertical}.resize{resize:both}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-row-reverse{flex-direction:row-reverse}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.content-center{align-content:center}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0{gap:0px}.gap-0\\.5{gap:2px}.gap-1{gap:4px}.gap-1\\.5{gap:6px}.gap-2{gap:8px}.gap-2\\.5{gap:10px}.gap-3{gap:12px}.gap-3\\.5{gap:14px}.gap-4{gap:16px}.gap-5{gap:20px}.gap-6{gap:24px}.gap-x-3{-moz-column-gap:12px;column-gap:12px}.gap-x-5{-moz-column-gap:20px;column-gap:20px}.gap-y-1{row-gap:4px}.gap-y-2{row-gap:8px}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(4px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(4px * var(--tw-space-y-reverse))}.space-y-1\\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(6px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(6px * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(8px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(8px * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(12px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(12px * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(16px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(16px * var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(20px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(20px * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(24px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(24px * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-line-subtle>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:hsl(var(--border-subtle) / var(--tw-divide-opacity, 1))}.self-start{align-self:flex-start}.self-end{align-self:flex-end}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.break-keep{word-break:keep-all}.\\!rounded-none{border-radius:0!important}.rounded{border-radius:4px}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-\\[18px\\]{border-radius:18px}.rounded-\\[20px\\]{border-radius:20px}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-none{border-radius:0}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-xs{border-radius:var(--radius-xs)}.rounded-b-md{border-bottom-right-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.rounded-t-md{border-top-left-radius:var(--radius-md);border-top-right-radius:var(--radius-md)}.border{border-width:1px}.border-0{border-width:0px}.border-2{border-width:2px}.border-y{border-top-width:1px;border-bottom-width:1px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l{border-left-width:1px}.border-l-2{border-left-width:2px}.border-l-4{border-left-width:4px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-t-0{border-top-width:0px}.border-dashed{border-style:dashed}.border-\\[hsl\\(144_8\\%_91\\%\\)\\]{--tw-border-opacity: 1;border-color:hsl(144 8% 91% / var(--tw-border-opacity, 1))}.border-brand{--tw-border-opacity: 1;border-color:hsl(var(--brand-hsl) / var(--tw-border-opacity, 1))}.border-brand-border{border-color:var(--brand-border)}.border-brand\\/30{border-color:hsl(var(--brand-hsl) / .3)}.border-brand\\/40{border-color:hsl(var(--brand-hsl) / .4)}.border-danger\\/20{border-color:hsl(var(--danger) / .2)}.border-danger\\/30{border-color:hsl(var(--danger) / .3)}.border-info\\/20{border-color:hsl(var(--info) / .2)}.border-info\\/30{border-color:hsl(var(--info) / .3)}.border-line{--tw-border-opacity: 1;border-color:hsl(var(--border-default) / var(--tw-border-opacity, 1))}.border-line-focus{--tw-border-opacity: 1;border-color:hsl(var(--border-focus) / var(--tw-border-opacity, 1))}.border-line-strong{--tw-border-opacity: 1;border-color:hsl(var(--border-strong) / var(--tw-border-opacity, 1))}.border-line-subtle{--tw-border-opacity: 1;border-color:hsl(var(--border-subtle) / var(--tw-border-opacity, 1))}.border-line-subtle\\/50{border-color:hsl(var(--border-subtle) / .5)}.border-line-subtle\\/90{border-color:hsl(var(--border-subtle) / .9)}.border-slate-100{--tw-border-opacity: 1;border-color:rgb(241 245 249 / var(--tw-border-opacity, 1))}.border-slate-200{--tw-border-opacity: 1;border-color:rgb(226 232 240 / var(--tw-border-opacity, 1))}.border-success{--tw-border-opacity: 1;border-color:hsl(var(--success) / var(--tw-border-opacity, 1))}.border-success\\/20{border-color:hsl(var(--success) / .2)}.border-success\\/30{border-color:hsl(var(--success) / .3)}.border-text-primary{--tw-border-opacity: 1;border-color:hsl(var(--text-primary) / var(--tw-border-opacity, 1))}.border-transparent{border-color:transparent}.border-warning\\/20{border-color:hsl(var(--warning) / .2)}.border-warning\\/30{border-color:hsl(var(--warning) / .3)}.border-white\\/70{border-color:#ffffffb3}.border-white\\/80{border-color:#fffc}.border-white\\/90{border-color:#ffffffe6}.border-l-brand{--tw-border-opacity: 1;border-left-color:hsl(var(--brand-hsl) / var(--tw-border-opacity, 1))}.\\!bg-success-surface{background-color:hsl(var(--success-surface))!important}.bg-\\[var\\(--overlay-backdrop-strong\\)\\]{background-color:var(--overlay-backdrop-strong)}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.bg-black\\/10{background-color:#0000001a}.bg-black\\/40{background-color:#0006}.bg-black\\/80{background-color:#000c}.bg-brand{--tw-bg-opacity: 1;background-color:hsl(var(--brand-hsl) / var(--tw-bg-opacity, 1))}.bg-brand-subtle{background-color:var(--brand-subtle)}.bg-brand\\/10{background-color:hsl(var(--brand-hsl) / .1)}.bg-brand\\/5{background-color:hsl(var(--brand-hsl) / .05)}.bg-danger{--tw-bg-opacity: 1;background-color:hsl(var(--danger) / var(--tw-bg-opacity, 1))}.bg-danger-subtle{background-color:hsl(var(--danger-subtle))}.bg-danger\\/10{background-color:hsl(var(--danger) / .1)}.bg-info-subtle{background-color:hsl(var(--info-subtle))}.bg-line{--tw-bg-opacity: 1;background-color:hsl(var(--border-default) / var(--tw-bg-opacity, 1))}.bg-line-subtle{--tw-bg-opacity: 1;background-color:hsl(var(--border-subtle) / var(--tw-bg-opacity, 1))}.bg-success{--tw-bg-opacity: 1;background-color:hsl(var(--success) / var(--tw-bg-opacity, 1))}.bg-success-subtle{background-color:hsl(var(--success-subtle))}.bg-success-surface{background-color:hsl(var(--success-surface))}.bg-surface-0{--tw-bg-opacity: 1;background-color:hsl(var(--surface-0) / var(--tw-bg-opacity, 1))}.bg-surface-1{--tw-bg-opacity: 1;background-color:hsl(var(--surface-1) / var(--tw-bg-opacity, 1))}.bg-surface-2{--tw-bg-opacity: 1;background-color:hsl(var(--surface-2) / var(--tw-bg-opacity, 1))}.bg-surface-3{--tw-bg-opacity: 1;background-color:hsl(var(--surface-3) / var(--tw-bg-opacity, 1))}.bg-surface-inset{--tw-bg-opacity: 1;background-color:hsl(var(--surface-inset) / var(--tw-bg-opacity, 1))}.bg-text-primary{--tw-bg-opacity: 1;background-color:hsl(var(--text-primary) / var(--tw-bg-opacity, 1))}.bg-text-tertiary{--tw-bg-opacity: 1;background-color:hsl(var(--text-tertiary) / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.bg-warning{--tw-bg-opacity: 1;background-color:hsl(var(--warning) / var(--tw-bg-opacity, 1))}.bg-warning-subtle{background-color:hsl(var(--warning-subtle))}.bg-warning\\/10{background-color:hsl(var(--warning) / .1)}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-white\\/10{background-color:#ffffff1a}.bg-white\\/25{background-color:#ffffff40}.fill-text-primary{fill:hsl(var(--text-primary) / 1)}.stroke-\\[3\\]{stroke-width:3}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.\\!p-0{padding:0!important}.p-0{padding:0}.p-0\\.5{padding:2px}.p-1{padding:4px}.p-1\\.5{padding:6px}.p-2{padding:8px}.p-3{padding:12px}.p-3\\.5{padding:14px}.p-4{padding:16px}.p-5{padding:20px}.p-6{padding:24px}.p-7{padding:28px}.p-8{padding:32px}.p-\\[18px\\]{padding:18px}.px-0{padding-left:0;padding-right:0}.px-1\\.5{padding-left:6px;padding-right:6px}.px-2{padding-left:8px;padding-right:8px}.px-2\\.5{padding-left:10px;padding-right:10px}.px-3{padding-left:12px;padding-right:12px}.px-4{padding-left:16px;padding-right:16px}.px-5{padding-left:20px;padding-right:20px}.px-6{padding-left:24px;padding-right:24px}.px-9{padding-left:36px;padding-right:36px}.py-0\\.5{padding-top:2px;padding-bottom:2px}.py-1{padding-top:4px;padding-bottom:4px}.py-1\\.5{padding-top:6px;padding-bottom:6px}.py-10{padding-top:40px;padding-bottom:40px}.py-16{padding-top:64px;padding-bottom:64px}.py-2{padding-top:8px;padding-bottom:8px}.py-2\\.5{padding-top:10px;padding-bottom:10px}.py-3{padding-top:12px;padding-bottom:12px}.py-3\\.5{padding-top:14px;padding-bottom:14px}.py-4{padding-top:16px;padding-bottom:16px}.py-5{padding-top:20px;padding-bottom:20px}.py-6{padding-top:24px;padding-bottom:24px}.py-8{padding-top:32px;padding-bottom:32px}.pb-1{padding-bottom:4px}.pb-3{padding-bottom:12px}.pb-4{padding-bottom:16px}.pl-0{padding-left:0}.pl-3{padding-left:12px}.pl-4{padding-left:16px}.pl-5{padding-left:20px}.pl-6{padding-left:24px}.pl-8{padding-left:32px}.pr-10{padding-right:40px}.pr-3{padding-right:12px}.pr-4{padding-right:16px}.pt-0{padding-top:0}.pt-1{padding-top:4px}.pt-2\\.5{padding-top:10px}.pt-4{padding-top:16px}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.align-middle{vertical-align:middle}.font-display{font-family:var(--font-display),ui-sans-serif,system-ui,sans-serif}.font-mono{font-family:var(--font-mono),ui-monospace,SFMono-Regular,Menlo,monospace}.font-sans{font-family:var(--font-sans),ui-sans-serif,system-ui,sans-serif}.text-2xs{font-size:clamp(10.5px,10.19px + .082vw,11.5px);line-height:1.333}.text-\\[0\\.72rem\\]{font-size:11.52px}.text-\\[1\\.45rem\\]{font-size:23.2px}.text-\\[1\\.75rem\\]{font-size:28px}.text-\\[10px\\]{font-size:10px}.text-\\[11px\\]{font-size:11px}.text-base{font-size:clamp(14px,13.39px + .163vw,16px);line-height:1.5}.text-lg{font-size:clamp(17px,16.39px + .163vw,19px);line-height:1.471}.text-sm{font-size:clamp(13px,12.54px + .122vw,14.5px);line-height:1.462}.text-xl{font-size:clamp(20px,19.08px + .245vw,23px);line-height:1.45}.text-xs{font-size:clamp(12px,11.69px + .082vw,13px);line-height:1.333}.font-black{font-weight:900}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing: tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-4{line-height:16px}.leading-5{line-height:20px}.leading-8{line-height:32px}.leading-\\[1\\.55\\]{line-height:1.55}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-tight{line-height:1.25}.text-\\[hsl\\(145_5\\%_42\\%\\)\\]{--tw-text-opacity: 1;color:hsl(145 5% 42% / var(--tw-text-opacity, 1))}.text-brand{--tw-text-opacity: 1;color:hsl(var(--brand-hsl) / var(--tw-text-opacity, 1))}.text-brand-fg{color:var(--brand-fg)}.text-brand\\/50{color:hsl(var(--brand-hsl) / .5)}.text-danger{--tw-text-opacity: 1;color:hsl(var(--danger) / var(--tw-text-opacity, 1))}.text-danger\\/80{color:hsl(var(--danger) / .8)}.text-info{--tw-text-opacity: 1;color:hsl(var(--info) / var(--tw-text-opacity, 1))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.text-slate-700{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity, 1))}.text-slate-900{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity, 1))}.text-success{--tw-text-opacity: 1;color:hsl(var(--success) / var(--tw-text-opacity, 1))}.text-text-disabled{--tw-text-opacity: 1;color:hsl(var(--text-disabled) / var(--tw-text-opacity, 1))}.text-text-inverse{--tw-text-opacity: 1;color:hsl(var(--text-inverse) / var(--tw-text-opacity, 1))}.text-text-primary{--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1))}.text-text-secondary{--tw-text-opacity: 1;color:hsl(var(--text-secondary) / var(--tw-text-opacity, 1))}.text-text-tertiary{--tw-text-opacity: 1;color:hsl(var(--text-tertiary) / var(--tw-text-opacity, 1))}.text-transparent{color:transparent}.text-warning{--tw-text-opacity: 1;color:hsl(var(--warning) / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-white\\/90{color:#ffffffe6}.underline{text-decoration-line:underline}.line-through{text-decoration-line:line-through}.underline-offset-2{text-underline-offset:2px}.underline-offset-4{text-underline-offset:4px}.accent-brand{accent-color:hsl(var(--brand-hsl) / 1)}.opacity-0{opacity:0}.opacity-100{opacity:1}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.opacity-80{opacity:.8}.\\!shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1) !important;--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color) !important;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)!important}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\\[0_24px_70px_-28px_rgba\\(16\\,24\\,18\\,0\\.45\\)\\]{--tw-shadow: 0 24px 70px -28px rgba(16,24,18,.45);--tw-shadow-colored: 0 24px 70px -28px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: var(--shadow-lg);--tw-shadow-colored: var(--shadow-lg);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: var(--shadow-md);--tw-shadow-colored: var(--shadow-md);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: var(--shadow-sm);--tw-shadow-colored: var(--shadow-sm);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: var(--shadow-xl);--tw-shadow-colored: var(--shadow-xl);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xs{--tw-shadow: var(--shadow-xs);--tw-shadow-colored: var(--shadow-xs);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.outline-2{outline-width:2px}.outline-offset-0{outline-offset:0px}.outline-transparent{outline-color:transparent}.ring{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-2{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-4{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-brand{--tw-ring-opacity: 1;--tw-ring-color: hsl(var(--brand-hsl) / var(--tw-ring-opacity, 1))}.ring-brand-subtle{--tw-ring-color: var(--brand-subtle)}.blur{--tw-blur: blur(8px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.\\!filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-\\[2px\\]{--tw-backdrop-blur: blur(2px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\\[background-color\\,border-color\\,color\\,box-shadow\\]{transition-property:background-color,border-color,color,box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\\[background-color\\,color\\,border-color\\,box-shadow\\,opacity\\]{transition-property:background-color,color,border-color,box-shadow,opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\\[background-color\\,color\\,border-color\\,box-shadow\\]{transition-property:background-color,color,border-color,box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\\[background-color\\,color\\,box-shadow\\]{transition-property:background-color,color,box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\\[background-color\\,color\\]{transition-property:background-color,color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\\[border-color\\,box-shadow\\,background-color\\]{transition-property:border-color,box-shadow,background-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\\[border-color\\,outline-color\\,box-shadow\\,background-color\\,transform\\]{transition-property:border-color,outline-color,box-shadow,background-color,transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\\[max-width\\,height\\,box-shadow\\,background-color\\,border-color\\,transform\\,opacity\\]{transition-property:max-width,height,box-shadow,background-color,border-color,transform,opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\\[max-width\\]{transition-property:max-width;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\\[opacity\\,transform\\]{transition-property:opacity,transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}.duration-\\[var\\(--issue-marker-expand-duration\\)\\]{transition-duration:var(--issue-marker-expand-duration)}.duration-fast{transition-duration:var(--duration-fast)}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-in-out{transition-timing-function:var(--ease-in-out)}.ease-out{transition-timing-function:var(--ease-out)}@keyframes float-gentle{0%,to{transform:translateY(0)}50%{transform:translateY(-2px)}}@keyframes toast-slide{0%{opacity:0;transform:translateY(-14px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes gradient-shift{0%,to{background-position:0% 50%}50%{background-position:100% 50%}}.issue-overlay-shell{border:1px solid hsl(var(--border-subtle));border-radius:var(--radius-lg);background:hsl(var(--surface-1));box-shadow:var(--shadow-md)}.issue-marker-breathe{animation:float-gentle 4.8s var(--ease-in-out) infinite}.issue-marker-breathe:hover,.issue-marker-breathe:focus-visible,.issue-marker-breathe:focus-within{animation-play-state:paused}.\\[overflow-wrap\\:anywhere\\]{overflow-wrap:anywhere}.marker\\:text-text-tertiary *::marker{color:hsl(var(--text-tertiary))}.marker\\:text-text-tertiary::marker{color:hsl(var(--text-tertiary))}.file\\:border-0::file-selector-button{border-width:0px}.file\\:bg-transparent::file-selector-button{background-color:transparent}.file\\:text-sm::file-selector-button{font-size:clamp(13px,12.54px + .122vw,14.5px);line-height:1.462}.file\\:font-medium::file-selector-button{font-weight:500}.file\\:text-text-primary::file-selector-button{--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1))}.placeholder\\:text-text-tertiary::-moz-placeholder{--tw-text-opacity: 1;color:hsl(var(--text-tertiary) / var(--tw-text-opacity, 1))}.placeholder\\:text-text-tertiary::placeholder{--tw-text-opacity: 1;color:hsl(var(--text-tertiary) / var(--tw-text-opacity, 1))}.first\\:mt-0:first-child{margin-top:0}.first\\:border-t-0:first-child{border-top-width:0px}.first\\:pt-0:first-child{padding-top:0}.focus-within\\:z-\\[10001\\]:focus-within{z-index:10001}.focus-within\\:border-line-focus:focus-within{--tw-border-opacity: 1;border-color:hsl(var(--border-focus) / var(--tw-border-opacity, 1))}.hover\\:z-\\[10001\\]:hover{z-index:10001}.hover\\:border-line:hover{--tw-border-opacity: 1;border-color:hsl(var(--border-default) / var(--tw-border-opacity, 1))}.hover\\:border-line-strong:hover{--tw-border-opacity: 1;border-color:hsl(var(--border-strong) / var(--tw-border-opacity, 1))}.hover\\:border-line-subtle:hover{--tw-border-opacity: 1;border-color:hsl(var(--border-subtle) / var(--tw-border-opacity, 1))}.hover\\:bg-\\[hsl\\(144_8\\%_96\\%\\)\\]:hover{--tw-bg-opacity: 1;background-color:hsl(144 8% 96% / var(--tw-bg-opacity, 1))}.hover\\:bg-blue-500:hover{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.hover\\:bg-brand-hover:hover{background-color:var(--brand-hover)}.hover\\:bg-brand\\/40:hover{background-color:hsl(var(--brand-hsl) / .4)}.hover\\:bg-danger-subtle:hover{background-color:hsl(var(--danger-subtle))}.hover\\:bg-danger\\/10:hover{background-color:hsl(var(--danger) / .1)}.hover\\:bg-slate-100:hover{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity, 1))}.hover\\:bg-slate-50:hover{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity, 1))}.hover\\:bg-surface-2:hover{--tw-bg-opacity: 1;background-color:hsl(var(--surface-2) / var(--tw-bg-opacity, 1))}.hover\\:bg-surface-2\\/60:hover{background-color:hsl(var(--surface-2) / .6)}.hover\\:bg-surface-3:hover{--tw-bg-opacity: 1;background-color:hsl(var(--surface-3) / var(--tw-bg-opacity, 1))}.hover\\:bg-surface-inset:hover{--tw-bg-opacity: 1;background-color:hsl(var(--surface-inset) / var(--tw-bg-opacity, 1))}.hover\\:bg-transparent:hover{background-color:transparent}.hover\\:bg-white\\/20:hover{background-color:#fff3}.hover\\:text-\\[hsl\\(150_10\\%_12\\%\\)\\]:hover{--tw-text-opacity: 1;color:hsl(150 10% 12% / var(--tw-text-opacity, 1))}.hover\\:text-brand-hover:hover{color:var(--brand-hover)}.hover\\:text-danger:hover{--tw-text-opacity: 1;color:hsl(var(--danger) / var(--tw-text-opacity, 1))}.hover\\:text-slate-900:hover{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity, 1))}.hover\\:text-text-primary:hover{--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1))}.hover\\:text-text-secondary:hover{--tw-text-opacity: 1;color:hsl(var(--text-secondary) / var(--tw-text-opacity, 1))}.hover\\:underline:hover{text-decoration-line:underline}.hover\\:opacity-90:hover{opacity:.9}.focus\\:border-brand:focus{--tw-border-opacity: 1;border-color:hsl(var(--brand-hsl) / var(--tw-border-opacity, 1))}.focus\\:bg-surface-2:focus{--tw-bg-opacity: 1;background-color:hsl(var(--surface-2) / var(--tw-bg-opacity, 1))}.focus\\:text-text-primary:focus{--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1))}.focus\\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus-visible\\:h-\\[var\\(--issue-sticker-hover-size\\)\\]:focus-visible{height:var(--issue-sticker-hover-size)}.focus-visible\\:max-w-\\[160px\\]:focus-visible{max-width:160px}.focus-visible\\:max-w-\\[480px\\]:focus-visible{max-width:480px}.focus-visible\\:border-line-focus:focus-visible{--tw-border-opacity: 1;border-color:hsl(var(--border-focus) / var(--tw-border-opacity, 1))}.focus-visible\\:border-transparent:focus-visible{border-color:transparent}.focus-visible\\:bg-slate-50:focus-visible{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity, 1))}.focus-visible\\:bg-surface-1:focus-visible{--tw-bg-opacity: 1;background-color:hsl(var(--surface-1) / var(--tw-bg-opacity, 1))}.focus-visible\\:bg-surface-2:focus-visible{--tw-bg-opacity: 1;background-color:hsl(var(--surface-2) / var(--tw-bg-opacity, 1))}.focus-visible\\:text-text-primary:focus-visible{--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1))}.focus-visible\\:shadow-focus:focus-visible{--tw-shadow: var(--shadow-focus);--tw-shadow-colored: var(--shadow-focus);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.focus-visible\\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\\:outline-line-focus:focus-visible{outline-color:hsl(var(--border-focus) / 1)}.focus-visible\\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\\:ring-4:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\\:ring-brand-subtle:focus-visible{--tw-ring-color: var(--brand-subtle)}.focus-visible\\:ring-white\\/40:focus-visible{--tw-ring-color: rgb(255 255 255 / .4)}.active\\:bg-brand-active:active{background-color:var(--brand-active)}.active\\:bg-brand\\/60:active{background-color:hsl(var(--brand-hsl) / .6)}.disabled\\:pointer-events-none:disabled{pointer-events:none}.disabled\\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\\:opacity-40:disabled{opacity:.4}.disabled\\:opacity-50:disabled{opacity:.5}.group\\/marker:hover .group-hover\\/marker\\:h-\\[var\\(--issue-sticker-hover-size\\)\\]{height:var(--issue-sticker-hover-size)}.group\\/marker:hover .group-hover\\/marker\\:max-w-\\[160px\\]{max-width:160px}.group\\/marker:hover .group-hover\\/marker\\:max-w-\\[480px\\]{max-width:480px}.group\\/marker:hover .group-hover\\/marker\\:translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\\/marker:hover .group-hover\\/marker\\:bg-brand-hover{background-color:var(--brand-hover)}.group\\/marker:hover .group-hover\\/marker\\:opacity-100{opacity:1}.group\\/marker:hover .group-hover\\/marker\\:shadow-xl{--tw-shadow: var(--shadow-xl);--tw-shadow-colored: var(--shadow-xl);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.group:focus-visible .group-focus-visible\\:translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:focus-visible .group-focus-visible\\:opacity-100{opacity:1}.group.toaster .group-\\[\\.toaster\\]\\:isolate{isolation:isolate}.group.toast .group-\\[\\.toast\\]\\:rounded-md,.group.toaster .group-\\[\\.toaster\\]\\:rounded-md{border-radius:var(--radius-md)}.group.toaster .group-\\[\\.toaster\\]\\:border{border-width:1px}.group.toaster .group-\\[\\.toaster\\]\\:\\!border-danger\\/30{border-color:hsl(var(--danger) / .3)!important}.group.toaster .group-\\[\\.toaster\\]\\:\\!border-info\\/30{border-color:hsl(var(--info) / .3)!important}.group.toaster .group-\\[\\.toaster\\]\\:\\!border-success\\/30{border-color:hsl(var(--success) / .3)!important}.group.toaster .group-\\[\\.toaster\\]\\:\\!border-warning\\/30{border-color:hsl(var(--warning) / .3)!important}.group.toaster .group-\\[\\.toaster\\]\\:border-line-subtle{--tw-border-opacity: 1;border-color:hsl(var(--border-subtle) / var(--tw-border-opacity, 1))}.group.toast .group-\\[\\.toast\\]\\:bg-brand{--tw-bg-opacity: 1;background-color:hsl(var(--brand-hsl) / var(--tw-bg-opacity, 1))}.group.toast .group-\\[\\.toast\\]\\:bg-surface-2{--tw-bg-opacity: 1;background-color:hsl(var(--surface-2) / var(--tw-bg-opacity, 1))}.group.toaster .group-\\[\\.toaster\\]\\:\\!bg-danger-subtle{background-color:hsl(var(--danger-subtle))!important}.group.toaster .group-\\[\\.toaster\\]\\:\\!bg-info-subtle{background-color:hsl(var(--info-subtle))!important}.group.toaster .group-\\[\\.toaster\\]\\:\\!bg-success-surface{background-color:hsl(var(--success-surface))!important}.group.toaster .group-\\[\\.toaster\\]\\:\\!bg-warning-subtle{background-color:hsl(var(--warning-subtle))!important}.group.toaster .group-\\[\\.toaster\\]\\:bg-surface-1{--tw-bg-opacity: 1;background-color:hsl(var(--surface-1) / var(--tw-bg-opacity, 1))}.group.toaster .group-\\[\\.toaster\\]\\:px-4{padding-left:16px;padding-right:16px}.group.toaster .group-\\[\\.toaster\\]\\:py-3{padding-top:12px;padding-bottom:12px}.group.toast .group-\\[\\.toast\\]\\:text-sm{font-size:clamp(13px,12.54px + .122vw,14.5px);line-height:1.462}.group.toast .group-\\[\\.toast\\]\\:text-xs{font-size:clamp(12px,11.69px + .082vw,13px);line-height:1.333}.group.toast .group-\\[\\.toast\\]\\:font-medium{font-weight:500}.group.toast .group-\\[\\.toast\\]\\:text-brand-fg{color:var(--brand-fg)}.group.toast .group-\\[\\.toast\\]\\:text-text-primary{--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1))}.group.toast .group-\\[\\.toast\\]\\:text-text-secondary{--tw-text-opacity: 1;color:hsl(var(--text-secondary) / var(--tw-text-opacity, 1))}.group.toaster .group-\\[\\.toaster\\]\\:text-text-primary{--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1))}.group.toaster .group-\\[\\.toaster\\]\\:shadow-sm{--tw-shadow: var(--shadow-sm);--tw-shadow-colored: var(--shadow-sm);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.group.toaster .group-\\[\\.toaster\\]\\:before\\:absolute:before{content:var(--tw-content);position:absolute}.group.toaster .group-\\[\\.toaster\\]\\:before\\:inset-0:before{content:var(--tw-content);top:0;right:0;bottom:0;left:0}.group.toaster .group-\\[\\.toaster\\]\\:before\\:-z-10:before{content:var(--tw-content);z-index:-10}.group.toaster .group-\\[\\.toaster\\]\\:before\\:rounded-md:before{content:var(--tw-content);border-radius:var(--radius-md)}.group.toaster .group-\\[\\.toaster\\]\\:before\\:bg-surface-1:before{content:var(--tw-content);--tw-bg-opacity: 1;background-color:hsl(var(--surface-1) / var(--tw-bg-opacity, 1))}.group.toaster .group-\\[\\.toaster\\]\\:before\\:content-\\[\\'\\'\\]:before{--tw-content: "";content:var(--tw-content)}.peer:focus-visible~.peer-focus-visible\\:shadow-focus{--tw-shadow: var(--shadow-focus);--tw-shadow-colored: var(--shadow-focus);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.peer:disabled~.peer-disabled\\:cursor-not-allowed{cursor:not-allowed}.peer:disabled~.peer-disabled\\:opacity-60{opacity:.6}.aria-disabled\\:pointer-events-none[aria-disabled=true]{pointer-events:none}.aria-disabled\\:opacity-40[aria-disabled=true]{opacity:.4}.data-\\[disabled\\]\\:pointer-events-none[data-disabled]{pointer-events:none}@keyframes dialog-out{0%{opacity:1;transform:translate(-50%,-50%) scale(1)}to{opacity:0;transform:translate(-50%,calc(-50% + 8px)) scale(.985)}}.data-\\[state\\=closed\\]\\:animate-dialog-out[data-state=closed]{animation:dialog-out var(--duration-out) var(--ease-out) forwards}@keyframes fade-out{0%{opacity:1}to{opacity:0}}.data-\\[state\\=closed\\]\\:animate-fade-out[data-state=closed]{animation:fade-out var(--duration-out) var(--ease-out) forwards}@keyframes scale-out{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.97)}}.data-\\[state\\=closed\\]\\:animate-scale-out[data-state=closed]{animation:scale-out var(--duration-out) var(--ease-out) forwards}@keyframes sheet-out-left{0%{transform:translate(0)}to{transform:translate(-100%);opacity:.6}}.data-\\[state\\=closed\\]\\:animate-sheet-out-left[data-state=closed]{animation:sheet-out-left var(--duration-base) var(--ease-out) forwards}@keyframes sheet-out-right{0%{transform:translate(0);opacity:1}to{transform:translate(100%);opacity:.6}}.data-\\[state\\=closed\\]\\:animate-sheet-out-right[data-state=closed]{animation:sheet-out-right var(--duration-base) var(--ease-out) forwards}.data-\\[state\\=delayed-open\\]\\:animate-scale-in[data-state=delayed-open]{animation:scale-in var(--duration-base) var(--ease-out) forwards}@keyframes dialog-in{0%{opacity:0;transform:translate(-50%,calc(-50% + 12px)) scale(.985)}to{opacity:1;transform:translate(-50%,-50%) scale(1)}}.data-\\[state\\=open\\]\\:animate-dialog-in[data-state=open]{animation:dialog-in var(--duration-base) var(--ease-out) forwards}@keyframes fade-in{0%{opacity:0}to{opacity:1}}.data-\\[state\\=open\\]\\:animate-fade-in[data-state=open]{animation:fade-in var(--duration-base) var(--ease-out) forwards}@keyframes scale-in{0%{opacity:0;transform:scale(.98)}to{opacity:1;transform:scale(1)}}.data-\\[state\\=open\\]\\:animate-scale-in[data-state=open]{animation:scale-in var(--duration-base) var(--ease-out) forwards}@keyframes sheet-in-left{0%{transform:translate(-100%);opacity:.6}to{transform:translate(0);opacity:1}}.data-\\[state\\=open\\]\\:animate-sheet-in-left[data-state=open]{animation:sheet-in-left var(--duration-base) var(--ease-out) forwards}@keyframes sheet-in-right{0%{transform:translate(100%);opacity:.6}to{transform:translate(0)}}.data-\\[state\\=open\\]\\:animate-sheet-in-right[data-state=open]{animation:sheet-in-right var(--duration-base) var(--ease-out) forwards}.data-\\[range-end\\=true\\]\\:rounded-md[data-range-end=true]{border-radius:var(--radius-md)}.data-\\[range-middle\\=true\\]\\:rounded-none[data-range-middle=true]{border-radius:0}.data-\\[range-start\\=true\\]\\:rounded-md[data-range-start=true]{border-radius:var(--radius-md)}.data-\\[state\\=open\\]\\:border-line-focus[data-state=open]{--tw-border-opacity: 1;border-color:hsl(var(--border-focus) / var(--tw-border-opacity, 1))}.data-\\[highlighted\\]\\:bg-surface-2[data-highlighted]{--tw-bg-opacity: 1;background-color:hsl(var(--surface-2) / var(--tw-bg-opacity, 1))}.data-\\[range-end\\=true\\]\\:bg-brand[data-range-end=true]{--tw-bg-opacity: 1;background-color:hsl(var(--brand-hsl) / var(--tw-bg-opacity, 1))}.data-\\[range-middle\\=true\\]\\:bg-transparent[data-range-middle=true]{background-color:transparent}.data-\\[range-start\\=true\\]\\:bg-brand[data-range-start=true],.data-\\[selected-single\\=true\\]\\:bg-brand[data-selected-single=true]{--tw-bg-opacity: 1;background-color:hsl(var(--brand-hsl) / var(--tw-bg-opacity, 1))}.data-\\[state\\=selected\\]\\:bg-brand-subtle[data-state=selected]{background-color:var(--brand-subtle)}.data-\\[today\\=true\\]\\:font-semibold[data-today=true]{font-weight:600}.data-\\[highlighted\\]\\:text-text-primary[data-highlighted]{--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1))}.data-\\[placeholder\\]\\:text-text-tertiary[data-placeholder]{--tw-text-opacity: 1;color:hsl(var(--text-tertiary) / var(--tw-text-opacity, 1))}.data-\\[range-end\\=true\\]\\:text-brand-fg[data-range-end=true]{color:var(--brand-fg)}.data-\\[range-middle\\=true\\]\\:text-text-primary[data-range-middle=true]{--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1))}.data-\\[range-start\\=true\\]\\:text-brand-fg[data-range-start=true],.data-\\[selected-single\\=true\\]\\:text-brand-fg[data-selected-single=true]{color:var(--brand-fg)}.data-\\[state\\=active\\]\\:text-text-primary[data-state=active],.data-\\[state\\=checked\\]\\:text-text-primary[data-state=checked]{--tw-text-opacity: 1;color:hsl(var(--text-primary) / var(--tw-text-opacity, 1))}.data-\\[today\\=true\\]\\:text-brand[data-today=true]{--tw-text-opacity: 1;color:hsl(var(--brand-hsl) / var(--tw-text-opacity, 1))}.data-\\[disabled\\]\\:opacity-50[data-disabled]{opacity:.5}.data-\\[state\\=open\\]\\:shadow-focus[data-state=open]{--tw-shadow: var(--shadow-focus);--tw-shadow-colored: var(--shadow-focus);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.data-\\[range-end\\=true\\]\\:hover\\:bg-brand-hover:hover[data-range-end=true],.data-\\[range-start\\=true\\]\\:hover\\:bg-brand-hover:hover[data-range-start=true],.data-\\[selected-single\\=true\\]\\:hover\\:bg-brand-hover:hover[data-selected-single=true]{background-color:var(--brand-hover)}.data-\\[range-end\\=true\\]\\:hover\\:text-brand-fg:hover[data-range-end=true],.data-\\[range-start\\=true\\]\\:hover\\:text-brand-fg:hover[data-range-start=true],.data-\\[selected-single\\=true\\]\\:hover\\:text-brand-fg:hover[data-selected-single=true]{color:var(--brand-fg)}@media(prefers-reduced-motion:reduce){.motion-reduce\\:animate-none{animation:none}}@media(min-width:640px){.sm\\:flex-row{flex-direction:row}.sm\\:items-start{align-items:flex-start}.sm\\:items-center{align-items:center}.sm\\:justify-end{justify-content:flex-end}.sm\\:justify-between{justify-content:space-between}.sm\\:gap-2\\.5{gap:10px}.sm\\:p-5{padding:20px}.sm\\:p-6{padding:24px}.sm\\:text-2xl{font-size:clamp(26px,24.78px + .327vw,30px);line-height:1.308}.sm\\:text-\\[2\\.1rem\\]{font-size:33.6px}}@media(min-width:768px){.md\\:flex{display:flex}.md\\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\\:grid-cols-\\[minmax\\(0\\,1fr\\)_minmax\\(260px\\,340px\\)\\]{grid-template-columns:minmax(0,1fr) minmax(260px,340px)}}.\\[\\&\\>\\*\\:not\\(summary\\)\\]\\:pl-4>*:not(summary){padding-left:16px}.\\[\\&\\>span\\]\\:line-clamp-1>span{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:1}.\\[\\&\\>tr\\]\\:last\\:border-b-0:last-child>tr{border-bottom-width:0px}.\\[\\&\\[data-state\\=open\\]\\>span\\[data-chevron\\]\\]\\:rotate-180[data-state=open]>span[data-chevron]{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.\\[\\&_\\:is\\(h1\\,h2\\,h3\\,h4\\,h5\\,h6\\,p\\,li\\,td\\,th\\,summary\\,blockquote\\)\\]\\:text-text-secondary :is(h1,h2,h3,h4,h5,h6,p,li,td,th,summary,blockquote){--tw-text-opacity: 1;color:hsl(var(--text-secondary) / var(--tw-text-opacity, 1))}.group.toaster .group-\\[\\.toaster\\]\\:\\[\\&_\\[data-icon\\]\\]\\:text-danger [data-icon]{--tw-text-opacity: 1;color:hsl(var(--danger) / var(--tw-text-opacity, 1))}.group.toaster .group-\\[\\.toaster\\]\\:\\[\\&_\\[data-icon\\]\\]\\:text-info [data-icon]{--tw-text-opacity: 1;color:hsl(var(--info) / var(--tw-text-opacity, 1))}.group.toaster .group-\\[\\.toaster\\]\\:\\[\\&_\\[data-icon\\]\\]\\:text-success [data-icon]{--tw-text-opacity: 1;color:hsl(var(--success) / var(--tw-text-opacity, 1))}.group.toaster .group-\\[\\.toaster\\]\\:\\[\\&_\\[data-icon\\]\\]\\:text-warning [data-icon]{--tw-text-opacity: 1;color:hsl(var(--warning) / var(--tw-text-opacity, 1))}.\\[\\&_tr\\]\\:border-b tr{border-bottom-width:1px}.\\[\\&_tr\\]\\:border-line-subtle tr{--tw-border-opacity: 1;border-color:hsl(var(--border-subtle) / var(--tw-border-opacity, 1))}li .\\[li_\\&\\]\\:pl-5{padding-left:20px}`;
39888
41281
  const sdkShadowCSS = ".issue-sticker-marker{pointer-events:auto}@keyframes highlight-pulse{0%,to{transform:scale(1);box-shadow:var(--shadow-md)}50%{transform:scale(1.3);box-shadow:var(--shadow-focus),var(--shadow-lg)}}.issue-sticker-marker.highlight-animation{animation:highlight-pulse .7s ease-in-out 3}";
39889
41282
  const contentPageCSS = `@layer issue-sticker-mode{html:has(>body.issue-sticker-mode-active),body.issue-sticker-mode-active,body.issue-sticker-mode-active *{cursor:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cpath d='M3 29 C3 22 3 20 3 15 a12 12 0 0 1 12-12 h2 a12 12 0 0 1 12 12 a12 12 0 0 1-12 12 H12 Z' fill='%231C221F' fill-opacity='0.35' stroke='%23ffffff' stroke-width='2' stroke-linejoin='round'/%3E%3C/svg%3E") 3 29,crosshair!important}body.issue-sticker-mode-active iframe{pointer-events:none!important}body.issue-sticker-mode-active .issue-sticker-mode-highlight{outline:2px solid hsl(160 64% 34%)!important;outline-offset:2px!important;background-color:#1f8e691f!important;transition:outline .1s ease,background-color .1s ease!important}}`;
39890
41283
  const PAGE_STYLE_ID = "issue-sticker-page-style";
39891
41284
  let root = null;
39892
41285
  let mountContainer = null;
41286
+ let detachHostGuards = null;
39893
41287
  function renderWidget(config) {
39894
41288
  if (!root || !mountContainer) {
39895
41289
  return;
@@ -39920,9 +41314,13 @@ function mountWidget(config) {
39920
41314
  }
39921
41315
  document.body.appendChild(hostElement);
39922
41316
  const shadow = createShadowRoot(hostElement, [globalsCSS, sdkShadowCSS]);
39923
- attachHostTypingGuard(shadow.shadowRoot);
39924
- attachHostPointerGuard(shadow.shadowRoot);
39925
- attachHostInteractionGuard(shadow.shadowRoot);
41317
+ detachHostGuards == null ? void 0 : detachHostGuards();
41318
+ const guards = [
41319
+ attachHostTypingGuard(shadow.shadowRoot),
41320
+ attachHostPointerGuard(shadow.shadowRoot),
41321
+ attachHostInteractionGuard(shadow.shadowRoot)
41322
+ ];
41323
+ detachHostGuards = () => guards.forEach((detach) => detach());
39926
41324
  mountContainer = shadow.mountContainer;
39927
41325
  mountContainer.style.width = "0";
39928
41326
  mountContainer.style.height = "0";
@@ -39938,6 +41336,8 @@ function unmountWidget() {
39938
41336
  root = null;
39939
41337
  }
39940
41338
  mountContainer = null;
41339
+ detachHostGuards == null ? void 0 : detachHostGuards();
41340
+ detachHostGuards = null;
39941
41341
  const host = document.getElementById(ISSUE_STICKER_HOST_ELEMENT_ID);
39942
41342
  if (host && getIssueStickerRuntimeOwner(host) !== ISSUE_STICKER_RUNTIME_OWNER.extension) {
39943
41343
  host.remove();