@solhun/feedback-kit-core 0.4.0 → 0.6.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/dist/index.cjs +623 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +332 -4
- package/dist/index.d.ts +332 -4
- package/dist/index.js +603 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 힌트가 어디에 뜨는가.
|
|
3
|
+
* `screen`=일반 모달(화면 전체를 묻는 흐름), `element`=요소 지목 모달, `both`=둘 다.
|
|
4
|
+
*/
|
|
5
|
+
type HintScope = "screen" | "element" | "both";
|
|
6
|
+
type HintPlatform = "web" | "app";
|
|
7
|
+
/**
|
|
8
|
+
* 요소 매칭 규칙. 다섯 축 전부 선택이고, 지정된 축 중 **하나라도** 맞으면 일치(OR).
|
|
9
|
+
* 모두 비어 있으면(축을 하나도 안 걸면) 요소 전역 힌트 — 어느 요소를 지목해도 후보다.
|
|
10
|
+
*/
|
|
11
|
+
interface HintElementRule {
|
|
12
|
+
tags?: readonly string[];
|
|
13
|
+
roles?: readonly string[];
|
|
14
|
+
classIncludes?: readonly string[];
|
|
15
|
+
componentIncludes?: readonly string[];
|
|
16
|
+
testIds?: readonly string[];
|
|
17
|
+
}
|
|
18
|
+
/** 힌트 카탈로그 항목 하나. 칩 하나에 대응한다. */
|
|
19
|
+
interface FeedbackHint {
|
|
20
|
+
id: string;
|
|
21
|
+
/** 칩에 보이는 짧은 라벨(최대 12자 — hintCharCount 기준). */
|
|
22
|
+
label: string;
|
|
23
|
+
/** 코멘트란에 채워지는 문장(최대 200자). */
|
|
24
|
+
draft: string;
|
|
25
|
+
scope: HintScope;
|
|
26
|
+
platforms: readonly HintPlatform[];
|
|
27
|
+
/** 비어 있으면 어디서나 후보(전역). 패턴 문법은 matchPath 참고. */
|
|
28
|
+
paths: readonly string[];
|
|
29
|
+
/** null 이면 요소 전역 힌트. */
|
|
30
|
+
element: HintElementRule | null;
|
|
31
|
+
/**
|
|
32
|
+
* 비활성 힌트인가(하드 필터로 제외된다). **선택 필드 — 생략(undefined)은 활성(true)과
|
|
33
|
+
* 동일하게 취급한다.** `staticHints` 로 직접 힌트를 넘기는 붙이는 쪽 코드가 관심도
|
|
34
|
+
* 없는 이 필드를 리터럴마다 채우게 만들지 않기 위해서다(의미는 필수였을 때와 1비트도
|
|
35
|
+
* 다르지 않다 — `parseHintCatalog` 는 여전히 `value !== false` 로 항상 값을 채우고,
|
|
36
|
+
* `rankHints` 는 여전히 `=== false` 일 때만 거른다).
|
|
37
|
+
*
|
|
38
|
+
* 서버는 이미 `active=true` 인 것만 내려주기로 돼 있어서 응답에서 이 필드 자체가
|
|
39
|
+
* 생략될 수 있다 — **생략되면 true(활성)로 본다.** 여기서 false 를 기본값으로 잡으면
|
|
40
|
+
* 필드가 없는 모든 힌트가 걸러져 어느 화면에서도 칩이 안 뜨는, 오류 없이 조용히
|
|
41
|
+
* 실패하는 상태가 된다. 클라이언트가 다시 거르는 이유는 서버 필터가 회귀했을 때
|
|
42
|
+
* (이미 비활성화한 힌트가 계속 내려올 때) 마지막 방어선이 있기 위해서다.
|
|
43
|
+
*/
|
|
44
|
+
active?: boolean;
|
|
45
|
+
}
|
|
46
|
+
/** 힌트 카탈로그 전체 + 표시 정책. */
|
|
47
|
+
interface HintCatalog {
|
|
48
|
+
/** 불투명 값. 캐시 식별·진단에만 쓴다 — 어떤 분기에도 넣지 않는다. */
|
|
49
|
+
version: number | null;
|
|
50
|
+
/** 한 번에 보여줄 칩 개수(3~6). */
|
|
51
|
+
maxDisplay: number;
|
|
52
|
+
/** 서버가 내려준 순서 그대로. 재정렬하지 않는다(동점 정렬의 마지막 기준). */
|
|
53
|
+
hints: readonly FeedbackHint[];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 랭킹이 요소에서 읽는 최소 형태. 코어의 `ElementInfo` 가 그대로 만족한다.
|
|
57
|
+
* 지목 모드가 아닌 화면에서는 랭킹 입력의 `element` 자체가 null 이다.
|
|
58
|
+
*/
|
|
59
|
+
interface ElementLike {
|
|
60
|
+
tag: string;
|
|
61
|
+
className: string | null;
|
|
62
|
+
attributes: Record<string, string>;
|
|
63
|
+
}
|
|
64
|
+
/** 랭킹 입력. 지목 모드가 아니면 element 는 null. */
|
|
65
|
+
interface HintRankInput {
|
|
66
|
+
platform: HintPlatform;
|
|
67
|
+
/** 웹은 location.pathname, 앱은 appPathFromNav(navPath). 모르면 null. */
|
|
68
|
+
path: string | null;
|
|
69
|
+
element: ElementLike | null;
|
|
70
|
+
/** React 컴포넌트 경로 문자열(웹). 없으면 null. */
|
|
71
|
+
components: string | null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 어댑터가 구현하는 힌트 수신원. 라쏘런 어댑터는 `GET <엔드포인트>/hints` 로 구현한다. */
|
|
75
|
+
interface HintSource {
|
|
76
|
+
/** 성공하면 원본 응답 객체, 304 면 "not-modified", 실패면 "fail". 던지지 않는다. */
|
|
77
|
+
fetchHints(etag: string | null, signal?: unknown): Promise<HintFetchResult>;
|
|
78
|
+
}
|
|
79
|
+
type HintFetchResult = {
|
|
80
|
+
kind: "ok";
|
|
81
|
+
raw: unknown;
|
|
82
|
+
etag: string | null;
|
|
83
|
+
} | {
|
|
84
|
+
kind: "not-modified";
|
|
85
|
+
}
|
|
86
|
+
/** permanent: 설정 문제(401·403 — 토큰 무효·Origin 불허)라 같은 세션에서 다시 시도해도 결과가 같다. */
|
|
87
|
+
| {
|
|
88
|
+
kind: "fail";
|
|
89
|
+
permanent?: boolean;
|
|
90
|
+
};
|
|
91
|
+
/** 저장소에 그대로 직렬화되는 캐시 레코드. */
|
|
92
|
+
interface HintCacheEntry {
|
|
93
|
+
version: number | null;
|
|
94
|
+
etag: string | null;
|
|
95
|
+
fetchedAt: number;
|
|
96
|
+
maxDisplay: number;
|
|
97
|
+
hints: readonly FeedbackHint[];
|
|
98
|
+
/** 마지막 실패 시각. 캐시에 함께 저장해야 새로고침으로 쿨다운이 무력화되지 않는다. */
|
|
99
|
+
failedAt: number | null;
|
|
100
|
+
}
|
|
101
|
+
interface HintProviderOpts {
|
|
102
|
+
source: HintSource | null;
|
|
103
|
+
/** 코어의 FeedbackStorage. 없으면 메모리만(인스턴스 생명주기 동안만 캐시). */
|
|
104
|
+
storage?: FeedbackStorage | null;
|
|
105
|
+
/** 캐시 키 접두사 뒤에 붙일 식별자(수집 소스별 분리 — 한 기기에서 여러 프로젝트를 오갈 때). */
|
|
106
|
+
cacheKey: string;
|
|
107
|
+
/** 설정으로 직접 준 힌트. 주면 source 를 아예 부르지 않는다(서버 목록과 합치지 않는다). */
|
|
108
|
+
staticHints?: readonly FeedbackHint[] | null;
|
|
109
|
+
now?: () => number;
|
|
110
|
+
warn?: ((message: string) => void) | null;
|
|
111
|
+
}
|
|
112
|
+
declare class HintProvider {
|
|
113
|
+
private readonly source;
|
|
114
|
+
private readonly storage;
|
|
115
|
+
private readonly storageKey;
|
|
116
|
+
private readonly now;
|
|
117
|
+
private readonly warn;
|
|
118
|
+
/** staticHints 가 있으면 이게 고정 카탈로그다 — source/storage 를 아예 보지 않는다. */
|
|
119
|
+
private readonly staticCatalog;
|
|
120
|
+
private entry;
|
|
121
|
+
/** refresh() 동시 호출을 한 번의 요청으로 합친다. */
|
|
122
|
+
private inflight;
|
|
123
|
+
/**
|
|
124
|
+
* 401/403 등 설정 문제로 영구 실패했는가 — 이 인스턴스가 살아 있는 동안 재요청하지
|
|
125
|
+
* 않는다. **저장소에는 남기지 않는다**(메모리만): 토큰을 고치고 재배포하면 그건 새
|
|
126
|
+
* 페이지 로드 = 새 인스턴스이므로 자연히 풀린다. 캐시에 박아두면 사용자가 캐시를
|
|
127
|
+
* 지우기 전까지 죽은 채로 남는다.
|
|
128
|
+
*/
|
|
129
|
+
private permanentlyFailed;
|
|
130
|
+
/**
|
|
131
|
+
* 실패(네트워크·타임아웃·5xx·401/403 전부 포함) 경고를 이미 냈는가 — 인스턴스당 1회로
|
|
132
|
+
* 제한한다. 5분 쿨다운마다 매번 경고를 찍으면 콘솔이 "실패했다"만 반복하는 노이즈가
|
|
133
|
+
* 된다("개발 빌드에서만 경고를 1회 남긴다" — 계약 원문).
|
|
134
|
+
*/
|
|
135
|
+
private warnedFailure;
|
|
136
|
+
constructor(opts: HintProviderOpts);
|
|
137
|
+
/** 캐시 복원. 마운트 직후 1회. 던지지 않는다. */
|
|
138
|
+
load(): Promise<void>;
|
|
139
|
+
/** 필요하면 네트워크. TTL 안이거나 쿨다운 중이면 아무것도 안 한다. 던지지 않는다. */
|
|
140
|
+
refresh(): Promise<void>;
|
|
141
|
+
/** 지금 쓸 수 있는 카탈로그. 없으면 null. 동기 — 요청을 기다리지 않는다. */
|
|
142
|
+
catalog(): HintCatalog | null;
|
|
143
|
+
private doRefresh;
|
|
144
|
+
private fetchWithTimeout;
|
|
145
|
+
private setEntry;
|
|
146
|
+
}
|
|
147
|
+
|
|
1
148
|
/** 제보 종류. `report`=일반 의견, `annotation`=화면 위 핀 찍힌 주석. */
|
|
2
149
|
type FeedbackKind = "report" | "annotation";
|
|
3
150
|
/**
|
|
@@ -221,6 +368,16 @@ interface FeedbackReport {
|
|
|
221
368
|
context: FeedbackContext;
|
|
222
369
|
/** 제출 시각(ISO 8601). */
|
|
223
370
|
createdAt: string;
|
|
371
|
+
/**
|
|
372
|
+
* 이 제보를 쓰면서 누른 제안 칩의 id 목록(순서 보존). 값을 가공 없이 그대로 싣는다 —
|
|
373
|
+
* 어느 제안이 실제로 채택됐는지 세는 유일한 신호다.
|
|
374
|
+
*
|
|
375
|
+
* 선택 필드인 이유는 계약 확장이지 의미가 선택적이라서가 아니다: 이 저장소 밖의 기존
|
|
376
|
+
* 테스트/호출부가 리터럴로 `FeedbackReport`를 만들 때 이 필드를 몰라도 컴파일이 깨지지
|
|
377
|
+
* 않아야 한다. 값을 채우는 쪽(`buildReport`)은 항상 배열을 넣는다 — `undefined`와 `[]`를
|
|
378
|
+
* 굳이 구분해서 읽지 않는다.
|
|
379
|
+
*/
|
|
380
|
+
hintIds?: readonly string[];
|
|
224
381
|
}
|
|
225
382
|
/**
|
|
226
383
|
* 어댑터 제출 결과.
|
|
@@ -254,6 +411,18 @@ interface FeedbackStorage {
|
|
|
254
411
|
/** 제보를 수집 백엔드로 보내는 어댑터. 호스트(또는 SDK)가 구현. */
|
|
255
412
|
interface FeedbackAdapter {
|
|
256
413
|
submit(report: FeedbackReport): Promise<SubmitResult>;
|
|
414
|
+
/**
|
|
415
|
+
* 제안 칩 카탈로그를 받아온다(선택). 구현하지 않은 어댑터는 힌트 기능이 그냥 꺼진다 —
|
|
416
|
+
* 제안은 거들 뿐이라 이것 때문에 제보가 막히면 안 된다.
|
|
417
|
+
* `submit`과 같은 절대 규칙: **던지지 않는다.** 실패는 `{kind:"fail"}`로 돌려준다.
|
|
418
|
+
*/
|
|
419
|
+
fetchHints?(etag: string | null): Promise<HintFetchResult>;
|
|
420
|
+
/**
|
|
421
|
+
* 힌트 캐시를 수집 소스별로 나누기 위한 식별자(선택). 없으면 위젯이 `"default"`를 쓴다.
|
|
422
|
+
* 한 기기에서 여러 프로젝트(수집 소스)를 오갈 때 이 값이 없으면 캐시가 섞여 남의
|
|
423
|
+
* 힌트가 뜬다.
|
|
424
|
+
*/
|
|
425
|
+
readonly hintsCacheKey?: string;
|
|
257
426
|
}
|
|
258
427
|
/** 제출 시점에 호출돼 사용자를 반환. 동기/비동기 모두 허용. */
|
|
259
428
|
type GetUserFn = () => FeedbackUser | Promise<FeedbackUser>;
|
|
@@ -348,6 +517,11 @@ interface ReportParts {
|
|
|
348
517
|
screenshot: FeedbackScreenshot | null;
|
|
349
518
|
pin: FeedbackPin | null;
|
|
350
519
|
element: ElementInfo | null;
|
|
520
|
+
/**
|
|
521
|
+
* 누른 제안 칩의 id 목록. 모달이 아닌 다른 호출부(테스트 등)는 생략할 수 있다 —
|
|
522
|
+
* 생략하면 아래 `buildReport`가 빈 배열로 채운다.
|
|
523
|
+
*/
|
|
524
|
+
hintIds?: readonly string[];
|
|
351
525
|
}
|
|
352
526
|
/** 제보 조립 옵션. 컨텍스트 조립 옵션과 동일하다(제보 쪽에 추가 입력이 없다). */
|
|
353
527
|
type BuildReportOpts = BuildContextOpts;
|
|
@@ -414,7 +588,12 @@ interface FeedbackQueueOpts {
|
|
|
414
588
|
declare function defaultBackoff(attempts: number, retryAfterMs: number | null, rateLimited?: boolean): number;
|
|
415
589
|
declare class FeedbackQueue {
|
|
416
590
|
private storage;
|
|
417
|
-
|
|
591
|
+
/**
|
|
592
|
+
* 읽기 전용으로 공개한다 — 위젯이 힌트 수신(`fetchHints`)처럼 어댑터의 선택 기능에
|
|
593
|
+
* 닿을 통로가 이것뿐이다. 전송 자체는 여전히 `submit()`/`flush()` 를 통해서만 한다
|
|
594
|
+
* (이 필드로 직접 `adapter.submit(...)` 을 부르면 큐의 재시도·백오프·멱등키를 건너뛴다).
|
|
595
|
+
*/
|
|
596
|
+
readonly adapter: FeedbackAdapter;
|
|
418
597
|
private flushIntervalMs;
|
|
419
598
|
private now;
|
|
420
599
|
private backoff;
|
|
@@ -904,6 +1083,7 @@ type ModalSubmitStatus = "idle" | "sending" | "sent" | "pending" | "failed";
|
|
|
904
1083
|
*/
|
|
905
1084
|
type PinAnchor = "picture" | "viewport";
|
|
906
1085
|
/** 모달 안에서 포커스를 받는 요소들의 고정 id. 렌더러가 그대로 매단다. */
|
|
1086
|
+
declare const MODAL_ACTION_HINT_TOGGLE = "hint-toggle";
|
|
907
1087
|
declare const MODAL_FIELD_COMMENT = "comment";
|
|
908
1088
|
declare const MODAL_FIELD_PRIORITY = "priority";
|
|
909
1089
|
declare const MODAL_ACTION_ATTACH = "attach";
|
|
@@ -942,6 +1122,14 @@ interface ReportModalState {
|
|
|
942
1122
|
focused: string | null;
|
|
943
1123
|
/** 모달이 닫힌 뒤 포커스를 돌려줄 곳. */
|
|
944
1124
|
restoreFocusTo: string | null;
|
|
1125
|
+
/** 지금 화면에 보여줄 칩(이미 눌린 것 포함). 접혀 있어도 값은 유지된다. */
|
|
1126
|
+
hints: readonly FeedbackHint[];
|
|
1127
|
+
/** 칩 줄이 펼쳐져 있는가. */
|
|
1128
|
+
hintsExpanded: boolean;
|
|
1129
|
+
/** 보여줄 힌트가 하나라도 있는가. false 면 접힘 버튼도 렌더하지 않는다. */
|
|
1130
|
+
hintsAvailable: boolean;
|
|
1131
|
+
/** 눌린 칩 id(순서 보존). 제보에 이 값이 실린다. */
|
|
1132
|
+
usedHintIds: readonly string[];
|
|
945
1133
|
}
|
|
946
1134
|
/** 모달이 큐에게 요구하는 최소 계약. `FeedbackQueue` 가 그대로 만족한다. */
|
|
947
1135
|
interface ModalQueueLike {
|
|
@@ -984,6 +1172,12 @@ declare class ReportModalController {
|
|
|
984
1172
|
private captureGeneration;
|
|
985
1173
|
/** 지금 들고 있는 핀의 기준. 핀이 없으면 의미 없다(기본 `picture`). */
|
|
986
1174
|
private pinAnchor;
|
|
1175
|
+
/**
|
|
1176
|
+
* 사용자가 [제안 ▾] 를 직접 건드렸는가(방향 무관). 한 번이라도 건드리면 이후
|
|
1177
|
+
* 타이핑에 의한 자동 접힘/펼침이 멈춘다 — "사람이 편 상태는 모달이 닫힐 때까지 유지"를
|
|
1178
|
+
* 이렇게 구현한다. `open()` 에서 매번 리셋된다.
|
|
1179
|
+
*/
|
|
1180
|
+
private userToggledHints;
|
|
987
1181
|
private state;
|
|
988
1182
|
constructor(opts: ReportModalOpts);
|
|
989
1183
|
getState(): ReportModalState;
|
|
@@ -1016,6 +1210,16 @@ declare class ReportModalController {
|
|
|
1016
1210
|
private close;
|
|
1017
1211
|
/** 상한을 넘겨도 값을 자르지 않는다. 거부는 하되 사용자가 쓴 글은 보존한다. */
|
|
1018
1212
|
setComment(value: string): void;
|
|
1213
|
+
/**
|
|
1214
|
+
* 코멘트가 비었는지에 따라 칩 줄 상태를 갱신하는 조각을 만든다. `setComment`·`applyHint`
|
|
1215
|
+
* 양쪽에서 쓴다.
|
|
1216
|
+
*
|
|
1217
|
+
* - 완전히 비워지면(trim 기준) 눌린 칩 기록을 버리고 다시 펼친다 — "지우고 처음부터 다시
|
|
1218
|
+
* 쓴 글"에 이전 제안을 귀속시키지 않기 위해서다. 사용자가 직접 편 상태여도 예외 없다.
|
|
1219
|
+
* - 비어 있지 않게 되면, 사용자가 직접 편 게 아닌 이상 접는다(좁은 화면에서 입력란을
|
|
1220
|
+
* 밀어내지 않으려고). 직접 편 상태(`userToggledHints`)면 손대지 않는다.
|
|
1221
|
+
*/
|
|
1222
|
+
private hintsPatchFor;
|
|
1019
1223
|
setPriority(priority: FeedbackPriority): void;
|
|
1020
1224
|
/**
|
|
1021
1225
|
* @param anchor 이 좌표의 기준. 기본은 `picture`(그림 위 탭) — 앱 핀 화면이 쓰는 값이다.
|
|
@@ -1029,6 +1233,20 @@ declare class ReportModalController {
|
|
|
1029
1233
|
attachFile(shot: FeedbackScreenshot): Promise<void>;
|
|
1030
1234
|
private runCapture;
|
|
1031
1235
|
private applyScreenshot;
|
|
1236
|
+
/**
|
|
1237
|
+
* 컨트롤러가 랭킹 결과를 밀어넣는다. **모달이 열려 있는 동안에는 무시한다** — 지목 대상을
|
|
1238
|
+
* 바꿔 새로 열기 직전에 호출되는 흐름이 정상이고, 열려 있는 모달의 칩을 도중에 바꾸면
|
|
1239
|
+
* 누르려던 칩이 손가락 밑에서 바뀐다(캐시 백그라운드 갱신도 이 경로로 들어올 수 있다).
|
|
1240
|
+
*/
|
|
1241
|
+
setHints(hints: readonly FeedbackHint[]): void;
|
|
1242
|
+
/**
|
|
1243
|
+
* 칩 누름. 이미 눌린 칩이면 아무 일도 하지 않는다(같은 문구가 두 번 이어붙는 걸 막는다).
|
|
1244
|
+
* 코멘트가 비어 있으면 초안으로 치환, 아니면 줄바꿈 뒤 이어붙인다 — 어느 쪽도 사용자가
|
|
1245
|
+
* 쓴 글을 지우지 않는다.
|
|
1246
|
+
*/
|
|
1247
|
+
applyHint(hintId: string): void;
|
|
1248
|
+
/** [제안 ▾] 토글. 방향과 무관하게 이후 자동 접힘/펼침을 멈춘다(`userToggledHints`). */
|
|
1249
|
+
toggleHints(): void;
|
|
1032
1250
|
tabNext(): string | null;
|
|
1033
1251
|
tabPrev(): string | null;
|
|
1034
1252
|
focus(id: string): boolean;
|
|
@@ -1189,11 +1407,17 @@ interface LassoResponseLike {
|
|
|
1189
1407
|
} | undefined;
|
|
1190
1408
|
text(): Promise<string>;
|
|
1191
1409
|
}
|
|
1192
|
-
/**
|
|
1410
|
+
/**
|
|
1411
|
+
* 최소한의 요청 형태. 표준 fetch 의 부분집합이라 그대로 꽂힌다.
|
|
1412
|
+
*
|
|
1413
|
+
* `body` 는 선택이다 — GET(힌트 조회)에는 본문이 없다. 실제 브라우저 `fetch` 는
|
|
1414
|
+
* `method: "GET"` 에 `body: ""` 를 같이 주면(빈 문자열이라도) TypeError 로 거절하므로,
|
|
1415
|
+
* GET 경로는 이 필드를 아예 생략해야 한다.
|
|
1416
|
+
*/
|
|
1193
1417
|
interface LassoRequestInit {
|
|
1194
1418
|
method: string;
|
|
1195
1419
|
headers: Record<string, string>;
|
|
1196
|
-
body
|
|
1420
|
+
body?: string;
|
|
1197
1421
|
keepalive?: boolean;
|
|
1198
1422
|
}
|
|
1199
1423
|
type LassoFetch = (url: string, init: LassoRequestInit) => Promise<LassoResponseLike>;
|
|
@@ -1258,6 +1482,12 @@ interface LassoReportContext {
|
|
|
1258
1482
|
id: string | null;
|
|
1259
1483
|
attributes: Record<string, string>;
|
|
1260
1484
|
} | null;
|
|
1485
|
+
/**
|
|
1486
|
+
* 이 제보를 쓰며 누른 제안 칩 id. 채택 신호를 나중에 집계하려면 지금부터 쌓여야 하므로
|
|
1487
|
+
* 값을 가공하지 않고 그대로 보낸다. 서버가 이 키를 보존하는지는 실호출로 확인 전까지
|
|
1488
|
+
* 가정하지 않는다(계약 원문 — 확인되기 전까지 "쌓이고 있다"고 말하지 않는다).
|
|
1489
|
+
*/
|
|
1490
|
+
hintIds: string[];
|
|
1261
1491
|
/** 앱 전용. 뷰어의 구현 정보 칸(기기·앱 버전·OTA·탐색 경로)이 이걸 읽는다. */
|
|
1262
1492
|
native: {
|
|
1263
1493
|
screenPath: string | null;
|
|
@@ -1394,4 +1624,102 @@ declare function guestUser(id: string): FeedbackUser;
|
|
|
1394
1624
|
*/
|
|
1395
1625
|
declare function uuidv4(): string;
|
|
1396
1626
|
|
|
1397
|
-
|
|
1627
|
+
/** 칩 라벨 상한(코드포인트). 넘으면 표시하지 않는다 — 자르면 뜻이 달라진다. */
|
|
1628
|
+
declare const HINT_LABEL_MAX_CHARS = 12;
|
|
1629
|
+
/** 코멘트에 채워지는 초안 문구 상한(코드포인트). */
|
|
1630
|
+
declare const HINT_DRAFT_MAX_CHARS = 200;
|
|
1631
|
+
/** maxDisplay 가 없거나 범위 밖일 때 쓰는 기본 표시 개수. */
|
|
1632
|
+
declare const HINT_DISPLAY_DEFAULT = 4;
|
|
1633
|
+
/** maxDisplay 허용 범위 하한. */
|
|
1634
|
+
declare const HINT_DISPLAY_MIN = 3;
|
|
1635
|
+
/** maxDisplay 허용 범위 상한. */
|
|
1636
|
+
declare const HINT_DISPLAY_MAX = 6;
|
|
1637
|
+
/** 카탈로그에서 취하는 최대 힌트 개수. 서버 결함이 랭킹 지연으로 번지지 않게 막는다. */
|
|
1638
|
+
declare const HINT_MAX_ITEMS = 300;
|
|
1639
|
+
/** 힌트 캐시 유효 기간(ms). 이 안에 마운트하면 네트워크 요청이 아예 안 나간다. */
|
|
1640
|
+
declare const HINT_TTL_MS: number;
|
|
1641
|
+
/** 힌트 요청 타임아웃(ms). 모달 열기·제보 전송과 무관하게 이 안에서 끝난다. */
|
|
1642
|
+
declare const HINT_TIMEOUT_MS = 5000;
|
|
1643
|
+
/** 힌트 요청 실패 후 재요청까지 대기(ms). 서버가 죽어 있을 때 부하를 더하지 않는다. */
|
|
1644
|
+
declare const HINT_FAIL_COOLDOWN_MS: number;
|
|
1645
|
+
/** 경로 정확 일치 점수. 요소 점수(30)보다 세다 — 같은 화면의 흔한 말이 가장 잘 맞는다. */
|
|
1646
|
+
declare const SCORE_PATH_EXACT = 40;
|
|
1647
|
+
/** 경로 패턴(`*`·`**`) 일치 점수. */
|
|
1648
|
+
declare const SCORE_PATH_PATTERN = 25;
|
|
1649
|
+
/** 요소 규칙 일치 점수(지목 모드에서만 더해진다). 축이 여럿 맞아도 한 번만. */
|
|
1650
|
+
declare const SCORE_ELEMENT = 30;
|
|
1651
|
+
|
|
1652
|
+
/**
|
|
1653
|
+
* NFC 정규화 후 코드포인트 수를 센다.
|
|
1654
|
+
*
|
|
1655
|
+
* 왜 `.length` 를 그대로 안 쓰나:
|
|
1656
|
+
* - macOS 는 한글을 자모 분리형(NFD)으로 넘긴다. "버튼 두께"(5글자)가 NFD 로 오면
|
|
1657
|
+
* 자모가 갈라져 코드포인트가 10개 가까이 된다 — 정규화 없이 세면 멀쩡한 라벨이
|
|
1658
|
+
* 상한(12자)을 넘겨 표시에서 사라진다.
|
|
1659
|
+
* - `.length`(UTF-16 코드유닛)로 세면 서로게이트 페어인 이모지가 2로 잡힌다.
|
|
1660
|
+
* 스프레드로 코드포인트 단위로 순회해야 이모지 1개를 1로 셀 수 있다.
|
|
1661
|
+
*/
|
|
1662
|
+
declare function hintCharCount(value: string): number;
|
|
1663
|
+
|
|
1664
|
+
/**
|
|
1665
|
+
* navPath 배열 → "/A/B/C".
|
|
1666
|
+
*
|
|
1667
|
+
* 실측상 앱 제보는 전부 `navPath`(탐색 스택, 바깥→안쪽 화면 이름 배열)를 갖고 있고,
|
|
1668
|
+
* `screenId`(빌드 플러그인 매핑)를 가진 건 0건이었다 — 그래서 매칭 키로 navPath 를
|
|
1669
|
+
* 쓴다. 마지막 항목만 쓰면 `/MainTabs/Home/**` 같은 패턴이 앱에서 영원히 안 맞으므로
|
|
1670
|
+
* 전체를 이어 붙인다.
|
|
1671
|
+
*/
|
|
1672
|
+
declare function appPathFromNav(navPath: readonly string[] | null | undefined): string | null;
|
|
1673
|
+
/**
|
|
1674
|
+
* 경로 패턴 매칭. `*` = 세그먼트 정확히 하나, `**` = 0개 이상(앞·중간·뒤 어디든 자유).
|
|
1675
|
+
*
|
|
1676
|
+
* 해석 불가한 패턴·경로(빈 문자열, `/` 로 시작하지 않음, `//` 같은 빈 세그먼트)는
|
|
1677
|
+
* **불일치로 처리한다.** 정규식을 안 쓰는 이유는 이 값을 사람이 관리 화면에서 손으로
|
|
1678
|
+
* 넣기 때문이다 — 잘못 쓴 정규식 하나가 조용히 아무것도 매칭하지 않는 상태를 만든다.
|
|
1679
|
+
*/
|
|
1680
|
+
declare function matchPath(pattern: string, path: string): "exact" | "pattern" | null;
|
|
1681
|
+
|
|
1682
|
+
/**
|
|
1683
|
+
* 요소 규칙 OR 매칭.
|
|
1684
|
+
*
|
|
1685
|
+
* - `rule` 이 null 이거나 다섯 축이 전부 비어 있으면(요소 전역 힌트) true.
|
|
1686
|
+
* - `element` 가 null 인데 규칙에 축이 있으면 false(비교할 대상이 없다).
|
|
1687
|
+
* - 지정된 축 중 **하나라도** 맞으면 true(OR) — AND 로 두면 사람이 관리 화면에서
|
|
1688
|
+
* 조합을 정확히 못 맞춰 아무 데서도 안 뜨는 힌트를 만들기 쉽다.
|
|
1689
|
+
* - 값이 배열이 아니면 그 축은 무시한다(=축이 없는 것처럼 취급).
|
|
1690
|
+
* - 절대 던지지 않는다 — 프로덕션 번들·커스텀 엘리먼트라 클래스/컴포넌트 정보를
|
|
1691
|
+
* 못 읽는 경우가 있고, 그래도 랭킹 계산 전체가 멈추면 안 된다.
|
|
1692
|
+
*/
|
|
1693
|
+
declare function matchElement(rule: HintElementRule | null, element: ElementLike | null, components: string | null): boolean;
|
|
1694
|
+
|
|
1695
|
+
/**
|
|
1696
|
+
* 힌트 응답 파싱.
|
|
1697
|
+
*
|
|
1698
|
+
* **항목 단위로 버린다.** 서버가 필드를 추가하거나 항목 하나가 깨져도 나머지 힌트는
|
|
1699
|
+
* 살아 있어야 한다 — 서버 결함 하나 때문에 그 화면의 제안이 통째로 사라지면 안 된다.
|
|
1700
|
+
* 응답 전체를 버리는 건 계약 자체를 벗어났을 때(`ok:false`, `hints` 가 배열이 아님)뿐이다.
|
|
1701
|
+
*
|
|
1702
|
+
* 글자수 상한(라벨 12자·초안 200자) 검사는 여기서 하지 않는다 — 그건 표시 여부를
|
|
1703
|
+
* 정하는 랭킹 단계(rankHints)의 하드 필터다. 파싱 단계는 "쓸 수 있는 모양인가"만 본다.
|
|
1704
|
+
*/
|
|
1705
|
+
declare function parseHintCatalog(raw: unknown): HintCatalog | null;
|
|
1706
|
+
|
|
1707
|
+
/**
|
|
1708
|
+
* 하드 필터 + 점수 + 정렬(동점은 경로 기반 회전) + 상한.
|
|
1709
|
+
*
|
|
1710
|
+
* 순수 함수 — 같은 입력(카탈로그·path·element)이면 항상 같은 결과를 낸다. 무작위는
|
|
1711
|
+
* 쓰지 않는다: 대신 **경로 문자열의 해시**로 동점 그룹 안의 시작점을 결정적으로 옮긴다.
|
|
1712
|
+
*
|
|
1713
|
+
* 왜 카탈로그 순서만으로는 안 되나: 전역 힌트(`paths:[]`)는 전부 0점 동점이다. 순수
|
|
1714
|
+
* 카탈로그 순서(인덱스 오름차순)만 쓰면 등록 순서 앞쪽 N개가 **어느 화면에서나 영원히**
|
|
1715
|
+
* 이기고, 뒤쪽은 아무리 많이 등록해도 절대 안 뜬다 — "많이 등록해서 알고리즘이 그중
|
|
1716
|
+
* 3~4개를 고른다"는 요구사항의 정반대(많이 등록할수록 죽은 힌트만 늘어난다)가 된다.
|
|
1717
|
+
* 화면(경로)마다 결정적으로 다른 오프셋을 주면 "전역 제안은 화면마다 나눠서 보여준다"가
|
|
1718
|
+
* 성립하면서도, 같은 화면은 항상 같은 칩을 보여준다(새로고침·재방문 불변).
|
|
1719
|
+
*
|
|
1720
|
+
* 회전은 **동점 그룹 안에서만** 순서를 바꾼다 — 점수가 다른 그룹끼리는 절대 안 섞인다.
|
|
1721
|
+
* 그래서 경로 점수(+40/+25)가 붙은 힌트는 회전된 전역 힌트보다 항상 앞에 온다.
|
|
1722
|
+
*/
|
|
1723
|
+
declare function rankHints(hints: readonly FeedbackHint[], input: HintRankInput, limit: number): FeedbackHint[];
|
|
1724
|
+
|
|
1725
|
+
export { type AdapterName, type AppInfo, BACKOFF_BASE_MS, BACKOFF_MAX_MS, type BuildContextOpts, type BuildReportOpts, COMMENT_MAX_CHARS, COMMENT_REQUIRED_MESSAGE, COMMENT_TOO_LONG_MESSAGE, type CaptureWithinLimitOpts, type ConfigWarning, type ConfigWarningCode, type ContextProviders, type ContextUser, DEFAULT_ADAPTER, DEFAULT_INTERNAL_ROLES, DEFAULT_POSITION, type DeviceInfo, type DiagLogEntry, type DiagNetworkEntry, type DiagRequestRef, DiagnosticsCollector, type DiagnosticsExcludeMatcher, type DiagnosticsInstallOpts, type DiagnosticsPayload, type DiagnosticsSource, type DisplayInfo, ENDPOINT_FROM_ADAPTER, type ElementInfo, type ElementLike, FLOATING_BUTTON_ID, type FeedbackAdapter, type FeedbackConfig, type FeedbackContext, type FeedbackHint, type FeedbackKind, type FeedbackPin, type FeedbackPriority, FeedbackQueue, type FeedbackQueueOpts, type FeedbackReport, type FeedbackScreenshot, type FeedbackStorage, type FeedbackUser, FocusRing, type GetCurrentScreenFn, type GetUserFn, HINT_DISPLAY_DEFAULT, HINT_DISPLAY_MAX, HINT_DISPLAY_MIN, HINT_DRAFT_MAX_CHARS, HINT_FAIL_COOLDOWN_MS, HINT_LABEL_MAX_CHARS, HINT_MAX_ITEMS, HINT_TIMEOUT_MS, HINT_TTL_MS, type HintCacheEntry, type HintCatalog, type HintElementRule, type HintFetchResult, type HintPlatform, HintProvider, type HintProviderOpts, type HintRankInput, type HintScope, type HintSource, KEEPALIVE_BODY_LIMIT_BYTES, LASSO_DEFAULT_ENDPOINT, LOG_BUFFER_LIMIT, type LassoAdapterOpts, type LassoEnvelope, type LassoFetch, type LassoReportContext, type LassoRequestInit, type LassoResponseLike, type LinearIssueInput, MAX_LOG_MESSAGE_CHARS, MAX_QUEUE_AGE_MS, MAX_QUEUE_ITEMS, MODAL_ACTION_ATTACH, MODAL_ACTION_CANCEL, MODAL_ACTION_HINT_TOGGLE, MODAL_ACTION_PICK, MODAL_ACTION_PIN, MODAL_ACTION_REMOVE_SCREENSHOT, MODAL_ACTION_RETRY, MODAL_ACTION_SEND, MODAL_FIELD_COMMENT, MODAL_FIELD_PRIORITY, type ModalQueueLike, type ModalSubmitStatus, NETWORK_BUFFER_LIMIT, type NativeContext, type NotionPageInput, PinController, type PixelPoint, type PixelSize, type QueueStatus, type QueueStatusListener, RATE_LIMIT_MIN_DELAY_MS, REDACTED, ReportModalController, type ReportModalListener, type ReportModalOpts, type ReportModalState, type ReportParts, type ResolvedConfig, RingBuffer, SCORE_ELEMENT, SCORE_PATH_EXACT, SCORE_PATH_PATTERN, SCREENSHOT_FAILED_MESSAGE, SCREENSHOT_MAX_BASE64_BYTES, SCREENSHOT_QUALITY_STEPS, SOURCE_ATTR, SUBMIT_DONE_MESSAGE, SUBMIT_FAILED_MESSAGE, SUBMIT_PENDING_MESSAGE, type ScreenshotCapture, type ScreenshotOutcome, type ScreenshotReencode, type ScreenshotStatus, type SourceLocation, type SourceMapping, type SubmitOutcome, type SubmitResult, TITLE_MAX_CHARS, type Visibility, type VisibilityEnv, type VisibilityFn, type WebContext, type WebContextInput, WidgetController, type WidgetControllerOpts, type WidgetCorner, type WidgetListener, type WidgetPlatform, type WidgetPosition, type WidgetScreen, type WidgetState, appPathFromNav, buildContext, buildLassoContext, buildLassoEnvelope, buildLinearIssue, buildNotionPage, buildReport, buildTitle, byteLengthOf, canUseKeepalive, captureWithinLimit, createLassoAdapter, defaultBackoff, denormalizePin, detectDevBuild, diagnosticsProviderFor, getOrCreateGuestId, guestUser, hintCharCount, isInternalUser, matchElement, matchPath, normalizePin, normalizeUser, parseHintCatalog, parseSourceAttr, rankHints, renderReportBody, resolveConfig, resolveContextUser, sanitizeUrl, screenshotBytes, sharedDiagnostics, shouldShowWidget, sourceFromElement, uuidv4 };
|