@stapel/chat-react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/MODULE.md +118 -0
  3. package/README.md +126 -0
  4. package/dist/api/chatApi.d.ts +49 -0
  5. package/dist/api/chatApi.d.ts.map +1 -0
  6. package/dist/api/chatApi.js +46 -0
  7. package/dist/api/chatApi.js.map +1 -0
  8. package/dist/api/extensions.d.ts +19 -0
  9. package/dist/api/extensions.d.ts.map +1 -0
  10. package/dist/api/extensions.js +19 -0
  11. package/dist/api/extensions.js.map +1 -0
  12. package/dist/api/generated/schema.d.ts +547 -0
  13. package/dist/api/generated/schema.d.ts.map +1 -0
  14. package/dist/api/generated/schema.js +2 -0
  15. package/dist/api/generated/schema.js.map +1 -0
  16. package/dist/api/types.d.ts +75 -0
  17. package/dist/api/types.d.ts.map +1 -0
  18. package/dist/api/types.js +2 -0
  19. package/dist/api/types.js.map +1 -0
  20. package/dist/default/ConversationListPanel.d.ts +22 -0
  21. package/dist/default/ConversationListPanel.d.ts.map +1 -0
  22. package/dist/default/ConversationListPanel.js +49 -0
  23. package/dist/default/ConversationListPanel.js.map +1 -0
  24. package/dist/default/ConversationThreadPanel.d.ts +21 -0
  25. package/dist/default/ConversationThreadPanel.d.ts.map +1 -0
  26. package/dist/default/ConversationThreadPanel.js +51 -0
  27. package/dist/default/ConversationThreadPanel.js.map +1 -0
  28. package/dist/default/ErrorAlert.d.ts +8 -0
  29. package/dist/default/ErrorAlert.d.ts.map +1 -0
  30. package/dist/default/ErrorAlert.js +26 -0
  31. package/dist/default/ErrorAlert.js.map +1 -0
  32. package/dist/default/StartChatButton.d.ts +17 -0
  33. package/dist/default/StartChatButton.d.ts.map +1 -0
  34. package/dist/default/StartChatButton.js +18 -0
  35. package/dist/default/StartChatButton.js.map +1 -0
  36. package/dist/default/index.d.ts +21 -0
  37. package/dist/default/index.d.ts.map +1 -0
  38. package/dist/default/index.js +18 -0
  39. package/dist/default/index.js.map +1 -0
  40. package/dist/flows/errors.d.ts +12 -0
  41. package/dist/flows/errors.d.ts.map +1 -0
  42. package/dist/flows/errors.js +13 -0
  43. package/dist/flows/errors.js.map +1 -0
  44. package/dist/flows/freshness.d.ts +69 -0
  45. package/dist/flows/freshness.d.ts.map +1 -0
  46. package/dist/flows/freshness.js +227 -0
  47. package/dist/flows/freshness.js.map +1 -0
  48. package/dist/flows/registry.d.ts +26 -0
  49. package/dist/flows/registry.d.ts.map +1 -0
  50. package/dist/flows/registry.js +24 -0
  51. package/dist/flows/registry.js.map +1 -0
  52. package/dist/headless/ChatProvider.d.ts +22 -0
  53. package/dist/headless/ChatProvider.d.ts.map +1 -0
  54. package/dist/headless/ChatProvider.js +18 -0
  55. package/dist/headless/ChatProvider.js.map +1 -0
  56. package/dist/headless/ConversationList.d.ts +51 -0
  57. package/dist/headless/ConversationList.d.ts.map +1 -0
  58. package/dist/headless/ConversationList.js +47 -0
  59. package/dist/headless/ConversationList.js.map +1 -0
  60. package/dist/headless/ConversationThread.d.ts +50 -0
  61. package/dist/headless/ConversationThread.d.ts.map +1 -0
  62. package/dist/headless/ConversationThread.js +64 -0
  63. package/dist/headless/ConversationThread.js.map +1 -0
  64. package/dist/headless/MessageComposer.d.ts +39 -0
  65. package/dist/headless/MessageComposer.d.ts.map +1 -0
  66. package/dist/headless/MessageComposer.js +45 -0
  67. package/dist/headless/MessageComposer.js.map +1 -0
  68. package/dist/headless/StartDirectChat.d.ts +40 -0
  69. package/dist/headless/StartDirectChat.d.ts.map +1 -0
  70. package/dist/headless/StartDirectChat.js +46 -0
  71. package/dist/headless/StartDirectChat.js.map +1 -0
  72. package/dist/i18n/errorsMap.d.ts +10 -0
  73. package/dist/i18n/errorsMap.d.ts.map +1 -0
  74. package/dist/i18n/errorsMap.js +20 -0
  75. package/dist/i18n/errorsMap.js.map +1 -0
  76. package/dist/i18n/es.d.ts +23 -0
  77. package/dist/i18n/es.d.ts.map +1 -0
  78. package/dist/i18n/es.js +75 -0
  79. package/dist/i18n/es.js.map +1 -0
  80. package/dist/i18n/generated/errors.es.gen.d.ts +16 -0
  81. package/dist/i18n/generated/errors.es.gen.d.ts.map +1 -0
  82. package/dist/i18n/generated/errors.es.gen.js +58 -0
  83. package/dist/i18n/generated/errors.es.gen.js.map +1 -0
  84. package/dist/i18n/generated/errors.gen.d.ts +359 -0
  85. package/dist/i18n/generated/errors.gen.d.ts.map +1 -0
  86. package/dist/i18n/generated/errors.gen.js +183 -0
  87. package/dist/i18n/generated/errors.gen.js.map +1 -0
  88. package/dist/i18n/generated/errors.ru.gen.d.ts +16 -0
  89. package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -0
  90. package/dist/i18n/generated/errors.ru.gen.js +58 -0
  91. package/dist/i18n/generated/errors.ru.gen.js.map +1 -0
  92. package/dist/i18n/keys.d.ts +78 -0
  93. package/dist/i18n/keys.d.ts.map +1 -0
  94. package/dist/i18n/keys.js +130 -0
  95. package/dist/i18n/keys.js.map +1 -0
  96. package/dist/i18n/ru.d.ts +28 -0
  97. package/dist/i18n/ru.d.ts.map +1 -0
  98. package/dist/i18n/ru.js +80 -0
  99. package/dist/i18n/ru.js.map +1 -0
  100. package/dist/index.d.ts +61 -0
  101. package/dist/index.d.ts.map +1 -0
  102. package/dist/index.js +55 -0
  103. package/dist/index.js.map +1 -0
  104. package/dist/model/context.d.ts +11 -0
  105. package/dist/model/context.d.ts.map +1 -0
  106. package/dist/model/context.js +16 -0
  107. package/dist/model/context.js.map +1 -0
  108. package/dist/model/limits.d.ts +12 -0
  109. package/dist/model/limits.d.ts.map +1 -0
  110. package/dist/model/limits.js +12 -0
  111. package/dist/model/limits.js.map +1 -0
  112. package/dist/model/mutations.d.ts +66 -0
  113. package/dist/model/mutations.d.ts.map +1 -0
  114. package/dist/model/mutations.js +156 -0
  115. package/dist/model/mutations.js.map +1 -0
  116. package/dist/model/queries.d.ts +33 -0
  117. package/dist/model/queries.d.ts.map +1 -0
  118. package/dist/model/queries.js +93 -0
  119. package/dist/model/queries.js.map +1 -0
  120. package/dist/model/queryKeys.d.ts +8 -0
  121. package/dist/model/queryKeys.d.ts.map +1 -0
  122. package/dist/model/queryKeys.js +26 -0
  123. package/dist/model/queryKeys.js.map +1 -0
  124. package/dist/model/readMarker.d.ts +25 -0
  125. package/dist/model/readMarker.d.ts.map +1 -0
  126. package/dist/model/readMarker.js +31 -0
  127. package/dist/model/readMarker.js.map +1 -0
  128. package/dist/model/runtime.d.ts +50 -0
  129. package/dist/model/runtime.d.ts.map +1 -0
  130. package/dist/model/runtime.js +27 -0
  131. package/dist/model/runtime.js.map +1 -0
  132. package/dist/model/threadWindow.d.ts +95 -0
  133. package/dist/model/threadWindow.d.ts.map +1 -0
  134. package/dist/model/threadWindow.js +100 -0
  135. package/dist/model/threadWindow.js.map +1 -0
  136. package/dist/nav/manifest.d.ts +23 -0
  137. package/dist/nav/manifest.d.ts.map +1 -0
  138. package/dist/nav/manifest.js +15 -0
  139. package/dist/nav/manifest.js.map +1 -0
  140. package/dist/realtime/chatSocket.d.ts +93 -0
  141. package/dist/realtime/chatSocket.d.ts.map +1 -0
  142. package/dist/realtime/chatSocket.js +199 -0
  143. package/dist/realtime/chatSocket.js.map +1 -0
  144. package/dist/realtime/frames.d.ts +123 -0
  145. package/dist/realtime/frames.d.ts.map +1 -0
  146. package/dist/realtime/frames.js +123 -0
  147. package/dist/realtime/frames.js.map +1 -0
  148. package/dist/realtime/streams.d.ts +49 -0
  149. package/dist/realtime/streams.d.ts.map +1 -0
  150. package/dist/realtime/streams.js +52 -0
  151. package/dist/realtime/streams.js.map +1 -0
  152. package/llms.txt +89 -0
  153. package/manifest.json +733 -0
  154. package/nav-manifest.json +25 -0
  155. package/package.json +114 -0
  156. package/src/analytics/generated/events.json +7 -0
  157. package/src/api/chatApi.ts +142 -0
  158. package/src/api/extensions.ts +18 -0
  159. package/src/api/generated/schema.ts +549 -0
  160. package/src/api/types.ts +87 -0
  161. package/src/default/ConversationListPanel.tsx +163 -0
  162. package/src/default/ConversationThreadPanel.tsx +245 -0
  163. package/src/default/ErrorAlert.tsx +43 -0
  164. package/src/default/StartChatButton.tsx +78 -0
  165. package/src/default/index.ts +20 -0
  166. package/src/flows/errors.ts +16 -0
  167. package/src/flows/freshness.ts +315 -0
  168. package/src/flows/registry.ts +36 -0
  169. package/src/headless/ChatProvider.tsx +23 -0
  170. package/src/headless/ConversationList.tsx +92 -0
  171. package/src/headless/ConversationThread.tsx +107 -0
  172. package/src/headless/MessageComposer.tsx +86 -0
  173. package/src/headless/StartDirectChat.tsx +75 -0
  174. package/src/i18n/errorsMap.ts +31 -0
  175. package/src/i18n/es.ts +95 -0
  176. package/src/i18n/generated/errors.es.gen.ts +64 -0
  177. package/src/i18n/generated/errors.gen.ts +214 -0
  178. package/src/i18n/generated/errors.json +408 -0
  179. package/src/i18n/generated/errors.ru.gen.ts +64 -0
  180. package/src/i18n/keys.ts +152 -0
  181. package/src/i18n/ru.ts +98 -0
  182. package/src/index.ts +189 -0
  183. package/src/model/context.tsx +25 -0
  184. package/src/model/limits.ts +11 -0
  185. package/src/model/mutations.ts +208 -0
  186. package/src/model/queries.ts +120 -0
  187. package/src/model/queryKeys.ts +32 -0
  188. package/src/model/readMarker.ts +32 -0
  189. package/src/model/runtime.ts +90 -0
  190. package/src/model/threadWindow.ts +173 -0
  191. package/src/nav/manifest.ts +36 -0
  192. package/src/realtime/chatSocket.ts +304 -0
  193. package/src/realtime/frames.ts +229 -0
  194. package/src/realtime/streams.ts +85 -0
  195. package/tsconfig.json +26 -0
@@ -0,0 +1,229 @@
1
+ /**
2
+ * The stapel-chat WebSocket protocol, typed.
3
+ *
4
+ * This is a MIRROR of the module's own contract, not an invention:
5
+ * `stapel_chat/consumers.py` (`ChatConsumer`) and `stapel_chat/realtime.py`
6
+ * (`message_frame`) are the source, and `MODULE.md` §"Realtime protocol"
7
+ * states it in one line:
8
+ *
9
+ * ```
10
+ * client → server: hello{last_seq} / send{body,attachments,reply_to} / ack{seq} / ping
11
+ * server → client: welcome{server_seq} / message{…seq} / replay_done{up_to_seq} / error{code,message} / pong
12
+ * ```
13
+ *
14
+ * The WS surface is invisible to `schema.json` (OpenAPI describes HTTP), so
15
+ * these types cannot be generated today — the realtime spec (§8) parks the
16
+ * frame contract in the `events.json` family for later. Until then this file
17
+ * is the pair's declared mirror and `test/frames.test.ts` pins it against the
18
+ * shapes the consumer actually sends.
19
+ *
20
+ * Nothing here imports React or `@stapel/core`: it is plain protocol, so the
21
+ * future `@stapel/realtime` substrate can subsume it without dragging the UI.
22
+ */
23
+
24
+ /**
25
+ * Widest resume gap the server replays inline (`consumers.REPLAY_LIMIT`).
26
+ * Past it the server answers `error{resync}` instead, and the client
27
+ * re-hydrates from the REST history. Mirrored here so the client can SAY that
28
+ * before the server has to.
29
+ */
30
+ export const CHAT_WS_REPLAY_LIMIT = 500;
31
+
32
+ /** `ChatConsumer.connect()` closes with this when `scope["user"]` is unset. */
33
+ export const CHAT_WS_CLOSE_UNAUTHENTICATED = 4401;
34
+ /** …and with this when the caller is not a participant of the conversation. */
35
+ export const CHAT_WS_CLOSE_NOT_PARTICIPANT = 4403;
36
+
37
+ // ── client → server ──────────────────────────────────────────────────────────
38
+
39
+ /** Resume: replay everything after `last_seq`, then go live. */
40
+ export interface ChatHelloFrame {
41
+ readonly type: "hello";
42
+ readonly last_seq: number;
43
+ }
44
+
45
+ /**
46
+ * Append a message over the socket.
47
+ *
48
+ * TYPED BUT NEVER SENT BY THIS CLIENT, on purpose. The frame is part of the
49
+ * module's contract, so a mirror that omitted it would be an incomplete
50
+ * mirror; but this pair posts messages over REST (`ChatApi.sendMessage`),
51
+ * where the answer is the persisted row and a refusal is a real error
52
+ * envelope. Over the socket a refusal is an `error` frame with a
53
+ * socket-local code (`empty` / `too_long` / `attachments_disabled` /
54
+ * `invalid_reply`) that is NOT in the module's error registry, so it has no
55
+ * i18n key and no remediation — the UI could not tell the person what went
56
+ * wrong. `createChatSocket` exposes no method that emits this.
57
+ */
58
+ export interface ChatSendFrame {
59
+ readonly type: "send";
60
+ readonly body?: string;
61
+ readonly attachments?: readonly string[];
62
+ readonly reply_to?: string | null;
63
+ }
64
+
65
+ /** Delivery acknowledgement — the server tracks it, nothing depends on it. */
66
+ export interface ChatAckFrame {
67
+ readonly type: "ack";
68
+ readonly seq: number;
69
+ }
70
+
71
+ /** Liveness probe; answered with `pong`. */
72
+ export interface ChatPingFrame {
73
+ readonly type: "ping";
74
+ }
75
+
76
+ export type ChatClientFrame =
77
+ | ChatHelloFrame
78
+ | ChatSendFrame
79
+ | ChatAckFrame
80
+ | ChatPingFrame;
81
+
82
+ // ── server → client ──────────────────────────────────────────────────────────
83
+
84
+ /** Answer to `hello`: the conversation's high-water mark right now. */
85
+ export interface ChatWelcomeFrame {
86
+ readonly type: "welcome";
87
+ readonly conversation_id: string;
88
+ readonly server_seq: number;
89
+ }
90
+
91
+ /**
92
+ * A message, replayed or live — the same frame either way (that is what makes
93
+ * the overlap after a resume harmless: it is seq-deduped on both ends).
94
+ * Field-for-field `stapel_chat.realtime.message_frame`.
95
+ */
96
+ export interface ChatMessageFrame {
97
+ readonly type: "message";
98
+ readonly message_id: string;
99
+ readonly conversation_id: string;
100
+ readonly sender_id: string | null;
101
+ readonly seq: number;
102
+ readonly kind: string;
103
+ readonly body: string;
104
+ readonly reply_to: string | null;
105
+ readonly attachments: readonly string[];
106
+ readonly created_at: string;
107
+ }
108
+
109
+ /** End of the replay window — everything up to `up_to_seq` has been sent. */
110
+ export interface ChatReplayDoneFrame {
111
+ readonly type: "replay_done";
112
+ readonly up_to_seq: number;
113
+ }
114
+
115
+ /**
116
+ * A protocol-level refusal. `code` is a SOCKET-LOCAL vocabulary
117
+ * (`resync` / `bad_type` / the send-path codes), NOT a `error.<status>.<code>`
118
+ * key from the module's error registry — which is exactly why the only one
119
+ * this pair acts on is `resync`.
120
+ */
121
+ export interface ChatErrorFrame {
122
+ readonly type: "error";
123
+ readonly code: string;
124
+ readonly message: string;
125
+ }
126
+
127
+ /** Answer to `ping`. */
128
+ export interface ChatPongFrame {
129
+ readonly type: "pong";
130
+ }
131
+
132
+ export type ChatServerFrame =
133
+ | ChatWelcomeFrame
134
+ | ChatMessageFrame
135
+ | ChatReplayDoneFrame
136
+ | ChatErrorFrame
137
+ | ChatPongFrame;
138
+
139
+ /** The one error code that means "stop replaying, re-read the journal". */
140
+ export const CHAT_WS_RESYNC = "resync";
141
+
142
+ // ── parsing (the wire is untrusted structured text) ──────────────────────────
143
+
144
+ function isRecord(value: unknown): value is Record<string, unknown> {
145
+ return typeof value === "object" && value !== null;
146
+ }
147
+
148
+ function str(value: unknown): string | null {
149
+ return typeof value === "string" ? value : null;
150
+ }
151
+
152
+ function num(value: unknown): number | null {
153
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
154
+ }
155
+
156
+ /**
157
+ * Narrow one decoded server frame, or `null` for anything this build does not
158
+ * understand (an unknown `type`, a missing field, a `seq` that is not a
159
+ * number). Unknown is DROPPED, never coerced: a frame this client cannot read
160
+ * must not advance the seq cursor, or the gap it leaves is invisible.
161
+ */
162
+ export function parseServerFrame(value: unknown): ChatServerFrame | null {
163
+ if (!isRecord(value)) return null;
164
+ switch (value.type) {
165
+ case "welcome": {
166
+ const conversationId = str(value.conversation_id);
167
+ const serverSeq = num(value.server_seq);
168
+ if (conversationId === null || serverSeq === null) return null;
169
+ return {
170
+ type: "welcome",
171
+ conversation_id: conversationId,
172
+ server_seq: serverSeq,
173
+ };
174
+ }
175
+ case "message": {
176
+ const seq = num(value.seq);
177
+ const messageId = str(value.message_id);
178
+ const conversationId = str(value.conversation_id);
179
+ const createdAt = str(value.created_at);
180
+ if (
181
+ seq === null ||
182
+ messageId === null ||
183
+ conversationId === null ||
184
+ createdAt === null
185
+ ) {
186
+ return null;
187
+ }
188
+ const attachments = Array.isArray(value.attachments)
189
+ ? value.attachments.filter((a): a is string => typeof a === "string")
190
+ : [];
191
+ return {
192
+ type: "message",
193
+ message_id: messageId,
194
+ conversation_id: conversationId,
195
+ sender_id: str(value.sender_id),
196
+ seq,
197
+ kind: str(value.kind) ?? "text",
198
+ body: str(value.body) ?? "",
199
+ reply_to: str(value.reply_to),
200
+ attachments,
201
+ created_at: createdAt,
202
+ };
203
+ }
204
+ case "replay_done": {
205
+ const upTo = num(value.up_to_seq);
206
+ if (upTo === null) return null;
207
+ return { type: "replay_done", up_to_seq: upTo };
208
+ }
209
+ case "error": {
210
+ const code = str(value.code);
211
+ if (code === null) return null;
212
+ return { type: "error", code, message: str(value.message) ?? "" };
213
+ }
214
+ case "pong":
215
+ return { type: "pong" };
216
+ default:
217
+ return null;
218
+ }
219
+ }
220
+
221
+ /** Decode a socket payload (JSON text) into a frame, or `null`. */
222
+ export function decodeServerFrame(data: unknown): ChatServerFrame | null {
223
+ if (typeof data !== "string") return null;
224
+ try {
225
+ return parseServerFrame(JSON.parse(data));
226
+ } catch {
227
+ return null;
228
+ }
229
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * How this pair NAMES the things it can be fresh about (realtime spec §7,
3
+ * "how a pair declares its channels"): typed stream keys plus the one rule
4
+ * that turns a key into a socket URL. Everything that decides *whether* a stream
5
+ * has a socket at all lives here, so the transport seam above can stay a
6
+ * two-line question.
7
+ */
8
+
9
+ /** A conversation's own journal — the module mounts a socket per thread. */
10
+ export interface ChatConversationStream {
11
+ readonly kind: "conversation";
12
+ readonly conversationId: string;
13
+ }
14
+
15
+ /**
16
+ * The caller's inbox (their conversation list).
17
+ *
18
+ * There is NO socket for this one, and that is a fact about the backend, not
19
+ * a gap in this pair: `stapel_chat.routing` mounts exactly one pattern,
20
+ * `ws/chat/<uuid:conversation_id>`, and the Channels group a message fans out
21
+ * to is `chat.conv.<id>` — per thread. Nothing broadcasts "your list moved".
22
+ * So the inbox is kept fresh by the polling half of the seam, and the seam is
23
+ * the reason no screen has to know that.
24
+ */
25
+ export interface ChatInboxStream {
26
+ readonly kind: "inbox";
27
+ }
28
+
29
+ export type ChatStreamKey = ChatConversationStream | ChatInboxStream;
30
+
31
+ export function chatConversationStream(
32
+ conversationId: string
33
+ ): ChatConversationStream {
34
+ return { kind: "conversation", conversationId };
35
+ }
36
+
37
+ export function chatInboxStream(): ChatInboxStream {
38
+ return { kind: "inbox" };
39
+ }
40
+
41
+ /** A stable string for a stream key — a React dependency, a log line. */
42
+ export function chatStreamId(stream: ChatStreamKey): string {
43
+ return stream.kind === "conversation"
44
+ ? `conversation:${stream.conversationId}`
45
+ : "inbox";
46
+ }
47
+
48
+ /**
49
+ * Derive the socket base from the REST base URL.
50
+ *
51
+ * The mount point is canonical (`stapel_chat/routing.py`, and the fleet
52
+ * convention `/ws/<mod>/...` from the realtime spec §8): the sockets sit at
53
+ * the HOST ROOT, not under the module's API prefix, so only the origin of
54
+ * `baseUrl` is used. `http` → `ws`, `https` → `wss`.
55
+ *
56
+ * Returns `null` when no origin can be resolved — a relative `baseUrl` with
57
+ * no `origin` (server-side rendering, a node test). `null` is not a failure:
58
+ * it means "this build cannot open a socket", and the transport seam polls.
59
+ */
60
+ export function deriveChatSocketBase(
61
+ baseUrl: string,
62
+ origin?: string | null
63
+ ): string | null {
64
+ try {
65
+ const url = new URL(baseUrl, origin ?? undefined);
66
+ const protocol =
67
+ url.protocol === "https:" || url.protocol === "wss:" ? "wss:" : "ws:";
68
+ return `${protocol}//${url.host}/ws/chat/`;
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * The socket URL for a stream, or `null` when the stream has none (the
76
+ * inbox) or no base could be derived.
77
+ */
78
+ export function chatSocketUrl(
79
+ base: string | null,
80
+ stream: ChatStreamKey
81
+ ): string | null {
82
+ if (base === null || stream.kind !== "conversation") return null;
83
+ const prefix = base.endsWith("/") ? base : `${base}/`;
84
+ return `${prefix}${encodeURIComponent(stream.conversationId)}`;
85
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "_comment": "Self-contained on purpose: standalone-buildable per frontend-standard §7. Mirrors the root tsconfig.base.json settings.",
4
+ "compilerOptions": {
5
+ "target": "ES2022",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "module": "ESNext",
8
+ "moduleResolution": "bundler",
9
+ "jsx": "react-jsx",
10
+ "strict": true,
11
+ "noUncheckedIndexedAccess": true,
12
+ "noImplicitOverride": true,
13
+ "exactOptionalPropertyTypes": true,
14
+ "isolatedModules": true,
15
+ "isolatedDeclarations": true,
16
+ "verbatimModuleSyntax": true,
17
+ "declaration": true,
18
+ "declarationMap": true,
19
+ "sourceMap": true,
20
+ "skipLibCheck": true,
21
+ "forceConsistentCasingInFileNames": true,
22
+ "outDir": "dist",
23
+ "rootDir": "src"
24
+ },
25
+ "include": ["src"]
26
+ }