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.
- package/CHANGELOG.md +89 -0
- package/dist/_commonjsHelpers-DWwsNxpa.js +8 -0
- package/dist/{externalUserPayload-BbOEmKZD.js → externalUserPayload-CdzgCq4t.js} +10700 -6882
- package/dist/index-DA6SoSp7.js +3009 -0
- package/dist/issue-sticker-headless-react.js +68 -12
- package/dist/issue-sticker-headless.js +554 -130
- package/dist/issue-sticker-react.js +2306 -906
- package/dist/{three-DQdLNs7v.js → three-FamoZZKK.js} +560 -33
- package/dist/{tooltip-DSCAsWIp.js → tooltip-BVA6ikuJ.js} +1 -1
- package/headless.d.ts +80 -4
- package/package.json +1 -1
|
@@ -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,
|
|
19
|
-
import {
|
|
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-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
1497
|
-
|
|
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
|
|
3702
|
-
finalizeThenableIfPossible(
|
|
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
|
|
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
|
|
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
|
-
|
|
5131
|
-
|
|
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
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
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
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
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
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
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
|
|
5269
|
-
|
|
5270
|
-
|
|
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
|
|
6051
|
-
const tenantId = ((_a = result.context) == null ? void 0 : _a.tenantId) ||
|
|
6052
|
-
const projectId = ((_b2 = result.context) == null ? void 0 : _b2.projectId) ||
|
|
6053
|
-
const accountId = ((_c2 = result.context) == null ? void 0 : _c2.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
|
|
6227
|
-
if (!
|
|
6228
|
-
window.clearTimeout(
|
|
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
|
-
|
|
7040
|
+
pending2.resolve(message.data);
|
|
6232
7041
|
} else {
|
|
6233
|
-
|
|
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
|
|
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 -
|
|
7399
|
-
state.value = state.done ? target :
|
|
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
|
|
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 -
|
|
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 :
|
|
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,
|
|
8239
|
+
function getGeneratorVelocity(resolveValue, t, current2) {
|
|
7431
8240
|
const prevT = Math.max(t - velocitySampleDuration, 0);
|
|
7432
|
-
return /* @__PURE__ */ velocityPerSecond(
|
|
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
|
|
7810
|
-
return getGeneratorVelocity((s) => this.generator.next(s).value, t,
|
|
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
|
|
9248
|
+
const current2 = sampleAnimation.sample(sampleTime).value;
|
|
8440
9249
|
const { name: name2 } = this.options;
|
|
8441
9250
|
if (element2 && name2)
|
|
8442
|
-
setStyle(element2, name2,
|
|
8443
|
-
motionValue2.setWithVelocity(sampleAnimation.sample(Math.max(0, sampleTime - delta)).value,
|
|
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
|
|
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] !==
|
|
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(
|
|
8724
|
-
this.current =
|
|
9532
|
+
setCurrent(current2) {
|
|
9533
|
+
this.current = current2;
|
|
8725
9534
|
this.updatedAt = time.now();
|
|
8726
|
-
if (this.canTrackVelocity === null &&
|
|
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,
|
|
8829
|
-
this.set(
|
|
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(
|
|
9088
|
-
const match = splitCSSVariableRegex.exec(
|
|
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(
|
|
9096
|
-
invariant(depth <= maxDepth, `Max CSS variable fallback depth detected in property "${
|
|
9097
|
-
const [token, fallback] = parseCSSVariable(
|
|
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 [
|
|
9118
|
-
definition2 = definition2(custom !== void 0 ? custom : props.custom,
|
|
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 [
|
|
9125
|
-
definition2 = definition2(custom !== void 0 ? custom : props.custom,
|
|
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
|
|
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
|
|
13599
|
-
while (
|
|
13600
|
-
const style = getComputedStyle(
|
|
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(
|
|
13603
|
-
x:
|
|
13604
|
-
y:
|
|
14411
|
+
this.scrollPositions.set(current2, {
|
|
14412
|
+
x: current2.scrollLeft,
|
|
14413
|
+
y: current2.scrollTop
|
|
13605
14414
|
});
|
|
13606
14415
|
}
|
|
13607
|
-
|
|
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
|
|
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:
|
|
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,
|
|
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
|
|
13842
|
-
if (percent.test(
|
|
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
|
-
|
|
14657
|
+
current2 = length * (parseFloat(current2) / 100);
|
|
13849
14658
|
}
|
|
13850
14659
|
}
|
|
13851
14660
|
}
|
|
13852
|
-
this.originPoint[axis] =
|
|
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
|
|
14071
|
-
axisValue.set(point2[axis] - mixNumber$1(min, max, 0.5) +
|
|
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 (!
|
|
15211
|
+
const { current: current2 } = this.node;
|
|
15212
|
+
if (!current2)
|
|
14404
15213
|
return;
|
|
14405
|
-
this.unmount = hover(
|
|
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 (!
|
|
15267
|
+
const { current: current2 } = this.node;
|
|
15268
|
+
if (!current2)
|
|
14460
15269
|
return;
|
|
14461
15270
|
const { globalTapTarget, propagate } = this.node.props;
|
|
14462
|
-
this.unmount = press(
|
|
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__ */
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
15776
|
-
else if (ref) ref.current = node2;
|
|
16370
|
+
dismissRef(node2);
|
|
15777
16371
|
},
|
|
15778
|
-
[
|
|
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
|
-
|
|
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(
|
|
16138
|
-
|
|
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:
|
|
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 && (
|
|
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
|
|
16535
|
-
|
|
16536
|
-
|
|
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
|
|
17191
|
+
return {};
|
|
16539
17192
|
}
|
|
16540
|
-
|
|
16541
|
-
|
|
16542
|
-
|
|
16543
|
-
|
|
16544
|
-
|
|
16545
|
-
|
|
16546
|
-
|
|
16547
|
-
|
|
16548
|
-
|
|
16549
|
-
|
|
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
|
|
16656
|
-
|
|
16657
|
-
|
|
16658
|
-
|
|
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 =
|
|
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
|
|
16754
|
-
|
|
16755
|
-
|
|
16756
|
-
|
|
16757
|
-
|
|
16758
|
-
|
|
16759
|
-
|
|
16760
|
-
|
|
16761
|
-
const
|
|
16762
|
-
|
|
16763
|
-
|
|
16764
|
-
|
|
16765
|
-
|
|
16766
|
-
|
|
16767
|
-
|
|
16768
|
-
|
|
16769
|
-
|
|
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
|
-
|
|
16779
|
-
|
|
16780
|
-
|
|
16781
|
-
|
|
16782
|
-
|
|
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
|
-
|
|
16786
|
-
|
|
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
|
|
16800
|
-
|
|
16801
|
-
|
|
16802
|
-
|
|
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
|
-
|
|
17508
|
+
function readSocketConnections() {
|
|
16806
17509
|
try {
|
|
16807
|
-
|
|
16808
|
-
|
|
16809
|
-
|
|
16810
|
-
|
|
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
|
|
17547
|
+
var _a;
|
|
16856
17548
|
assertSubmittable(data);
|
|
16857
|
-
|
|
16858
|
-
|
|
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
|
|
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 (((
|
|
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:
|
|
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
|
|
17168
|
-
|
|
17169
|
-
|
|
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
|
|
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((
|
|
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__ */
|
|
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
|
-
(
|
|
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
|
-
|
|
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((
|
|
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((
|
|
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((
|
|
18121
|
-
...
|
|
18921
|
+
setJiraFields((current2) => ({
|
|
18922
|
+
...current2,
|
|
18122
18923
|
[fieldId]: value
|
|
18123
18924
|
}));
|
|
18124
18925
|
},
|
|
18125
|
-
onDefaultsResolved: (defaults) => setJiraFields((
|
|
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((
|
|
18150
|
-
...
|
|
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((
|
|
18165
|
-
onDefaultsResolved: (defaults) => setCustomFieldValues((
|
|
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
|
|
18179
|
-
|
|
18180
|
-
|
|
18181
|
-
|
|
18182
|
-
}
|
|
18183
|
-
|
|
18184
|
-
|
|
18185
|
-
|
|
18186
|
-
|
|
18187
|
-
|
|
18188
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
26623
|
+
let current2 = token;
|
|
25666
26624
|
let adjust = 0;
|
|
25667
26625
|
let start = 0;
|
|
25668
26626
|
const breaks = [start];
|
|
25669
|
-
while (
|
|
25670
|
-
while (events.get(++startPosition)[1] !==
|
|
26627
|
+
while (current2) {
|
|
26628
|
+
while (events.get(++startPosition)[1] !== current2) {
|
|
25671
26629
|
}
|
|
25672
26630
|
startPositions.push(startPosition);
|
|
25673
|
-
if (!
|
|
25674
|
-
stream = context.sliceStream(
|
|
25675
|
-
if (!
|
|
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(
|
|
26637
|
+
tokenizer.defineSkip(current2.start);
|
|
25680
26638
|
}
|
|
25681
|
-
if (
|
|
26639
|
+
if (current2._isInFirstContentOfListItem) {
|
|
25682
26640
|
tokenizer._gfmTasklistFirstContentOfListItem = true;
|
|
25683
26641
|
}
|
|
25684
26642
|
tokenizer.write(stream);
|
|
25685
|
-
if (
|
|
26643
|
+
if (current2._isInFirstContentOfListItem) {
|
|
25686
26644
|
tokenizer._gfmTasklistFirstContentOfListItem = void 0;
|
|
25687
26645
|
}
|
|
25688
26646
|
}
|
|
25689
|
-
previous2 =
|
|
25690
|
-
|
|
26647
|
+
previous2 = current2;
|
|
26648
|
+
current2 = current2.next;
|
|
25691
26649
|
}
|
|
25692
|
-
|
|
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
|
-
|
|
25701
|
-
|
|
25702
|
-
|
|
26658
|
+
current2._tokenizer = void 0;
|
|
26659
|
+
current2.previous = void 0;
|
|
26660
|
+
current2 = current2.next;
|
|
25703
26661
|
}
|
|
25704
26662
|
}
|
|
25705
26663
|
tokenizer.events = [];
|
|
25706
|
-
if (
|
|
25707
|
-
|
|
25708
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
] }) :
|
|
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) =>
|
|
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(
|
|
34962
|
-
|
|
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((
|
|
37786
|
+
setActiveLogSection((current2) => active ? "network" : current2 === "network" ? null : current2);
|
|
36539
37787
|
}, []);
|
|
36540
37788
|
const handleConsoleDetailActiveChange = useCallback((active) => {
|
|
36541
|
-
setActiveLogSection((
|
|
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:
|
|
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
|
|
36805
|
-
for (let depth = 0;
|
|
36806
|
-
if (
|
|
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 (
|
|
36810
|
-
const style = view == null ? void 0 : view.getComputedStyle(
|
|
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 (
|
|
36813
|
-
|
|
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((
|
|
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((
|
|
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.
|
|
37851
|
-
|
|
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
|
-
|
|
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 ?
|
|
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
|
|
40831
|
+
const current2 = buttons.indexOf(document.activeElement);
|
|
39484
40832
|
const delta = e.key === "ArrowRight" ? 1 : -1;
|
|
39485
|
-
const next = (
|
|
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 === "
|
|
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
|
-
|
|
39924
|
-
|
|
39925
|
-
|
|
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();
|