@stapel/chat-react 0.1.0 → 0.3.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 (44) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/LICENSE +21 -0
  3. package/MODULE.md +32 -0
  4. package/README.md +21 -0
  5. package/dist/default/SignInLink.d.ts +28 -0
  6. package/dist/default/SignInLink.d.ts.map +1 -0
  7. package/dist/default/SignInLink.js +17 -0
  8. package/dist/default/SignInLink.js.map +1 -0
  9. package/dist/default/StartChatButton.d.ts +7 -3
  10. package/dist/default/StartChatButton.d.ts.map +1 -1
  11. package/dist/default/StartChatButton.js +3 -2
  12. package/dist/default/StartChatButton.js.map +1 -1
  13. package/dist/default/index.d.ts +2 -0
  14. package/dist/default/index.d.ts.map +1 -1
  15. package/dist/default/index.js +1 -0
  16. package/dist/default/index.js.map +1 -1
  17. package/dist/flows/freshness.js +2 -2
  18. package/dist/headless/StartDirectChat.d.ts +5 -0
  19. package/dist/headless/StartDirectChat.d.ts.map +1 -1
  20. package/dist/headless/StartDirectChat.js +29 -2
  21. package/dist/headless/StartDirectChat.js.map +1 -1
  22. package/dist/i18n/es.d.ts.map +1 -1
  23. package/dist/i18n/es.js +3 -0
  24. package/dist/i18n/es.js.map +1 -1
  25. package/dist/i18n/keys.d.ts +6 -0
  26. package/dist/i18n/keys.d.ts.map +1 -1
  27. package/dist/i18n/keys.js +9 -0
  28. package/dist/i18n/keys.js.map +1 -1
  29. package/dist/i18n/ru.d.ts.map +1 -1
  30. package/dist/i18n/ru.js +3 -0
  31. package/dist/i18n/ru.js.map +1 -1
  32. package/dist/model/runtime.d.ts +1 -1
  33. package/llms.txt +1 -1
  34. package/manifest.json +4 -1
  35. package/nav-manifest.json +1 -1
  36. package/package.json +14 -14
  37. package/src/analytics/generated/events.json +1 -1
  38. package/src/default/SignInLink.tsx +54 -0
  39. package/src/default/StartChatButton.tsx +11 -4
  40. package/src/default/index.ts +2 -0
  41. package/src/headless/StartDirectChat.tsx +36 -1
  42. package/src/i18n/es.ts +3 -0
  43. package/src/i18n/keys.ts +9 -0
  44. package/src/i18n/ru.ts +3 -0
package/CHANGELOG.md CHANGED
@@ -1 +1,96 @@
1
1
  # @stapel/chat-react
2
+
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 3e2e2a3: A blocked control now carries the door, not just the reason: `signIn`
8
+
9
+ `actionBlocked` ended the grey-rectangle incident by making every switched-off
10
+ control state its reason. It did not end the next one. "Sign in to save this",
11
+ "sign in to leave a review", "sign in to message the seller" are reasons whose
12
+ next action is a LINK, and no pair took one — so the storefront had to put its
13
+ own notice a screen away from each of the three controls it was about, and
14
+ named it a gap rather than shipping it (Wave D, G-3).
15
+
16
+ All three now take the same prop, core's `SignInCta`:
17
+
18
+ ```tsx
19
+ <ListingCard listing={row} signIn={{ href: `/login?next=${here}` }} />
20
+ <ReviewsPanel target={target} signIn={{ href: `/login?next=${here}` }} />
21
+ <StartChatButton sellerId={sellerId} signIn={{ onSignIn: () => openModal() }} />
22
+ ```
23
+
24
+ `{href}` **or** `{onSignIn}`, never both. Omit it and the reason renders alone,
25
+ with no trailing whitespace where the link is not — a host with no sign-in
26
+ route is a supported host.
27
+
28
+ Two more things each pair had to fix to make the door reachable:
29
+
30
+ - **listings**: the favourite's reason lived only in a `title` on a DISABLED
31
+ button, which receives no pointer events in any browser — core's own
32
+ `actionGate.ts` calls that "a reason nobody can read". It is now text beside
33
+ the heart (`listings-card-favorite-blocked`), with the link inside it. The
34
+ heart is still never hidden from a visitor.
35
+ - **chat**: `StartDirectChat` had no mandate gate at all, so a visitor could
36
+ press "message the seller" and collect a 401 — a refusal delivered at the one
37
+ moment it is useless. The axis is now the first arm of its `firstBlock`, read
38
+ through core's `MandateSource` seam. `member` may write; `guest`/`anonymous`
39
+ are told to sign in; `asking` says we are still asking. `unavailable` stays
40
+ AVAILABLE on purpose: that is what core answers outside a `<MandateProvider>`
41
+ too, and a host that never wired the axis must not lose its button — "we
42
+ could not ask" is not "you may not". This raises chat-react's `@stapel/core`
43
+ floor to `>=0.15.0`, where `useMandate`/`matchMandate` shipped.
44
+
45
+ The link's LABEL is each pair's own (`listings.card.sign_in`,
46
+ `reviews.form.sign_in`, `chat.start.sign_in`), in all three locales — core
47
+ floors `en` and `ru`, and these pairs also ship `es`.
48
+
49
+ ## 0.2.0
50
+
51
+ ### Minor Changes
52
+
53
+ - ca35e19: New pair: `@stapel/chat-react` — the React half of `stapel-chat`, wiring both
54
+ of its transports behind one seam.
55
+
56
+ The storefront spec ruled polling-only for chat v1, and it was right about the
57
+ fleet it surveyed: the resumable consumer existed but `stapel_chat.routing`
58
+ exported nothing, so no host could mount it. stapel-chat 0.2.2 ships that mount
59
+ (`ws/chat/<uuid:conversation_id>`). So this pair carries **two transports and
60
+ one seam** — `useChatFreshness(streamKey, mapToQueryKeys, { fallbackRefetchInterval })`,
61
+ deliberately the signature the realtime substrate reserves for
62
+ `useSignalInvalidate`:
63
+
64
+ - a typed client for the module's own protocol — `hello{last_seq}` → `welcome`
65
+ → replay → `replay_done` → live frames, seq-deduped on both ends, resume from
66
+ the cursor the STORE holds (not the one the socket opened with), `error{resync}`
67
+ forwarded verbatim, close codes 4401/4403 treated as answers rather than
68
+ faults (no reconnect), everything else reconnected with jittered backoff;
69
+ - polling by `seq`, visibility-aware, with exponential backoff on consecutive
70
+ failures — used whenever the socket is not carrying the stream, and for the
71
+ inbox, which has no socket at all (the module fans out per thread).
72
+
73
+ Both ends do the same thing with what they learn: refetch the thread query,
74
+ whose query function advances the window BY SEQ
75
+ (`?direction=prev&anchor=<tip>`). The screens are written once and the tests
76
+ run against both transports. Writes never go over the socket: the `send` frame
77
+ is typed (a mirror must be complete) and never emitted, because its refusals
78
+ carry socket-local codes with no i18n key and no remediation, while
79
+ `POST …/messages` answers with the persisted row and a real error envelope.
80
+
81
+ Surface: `<ConversationList>` (server-computed unread counts, as a LoadState so
82
+ a badge cannot read "0" during an outage), `<ConversationThread>` (a contiguous
83
+ seq-ordered window — a hole is re-read, never stitched — with backfill and an
84
+ automatic, monotonic read marker), `<MessageComposer>` (code-point length
85
+ counting, so an emoji is one character on both sides of the wire), and
86
+ `<StartDirectChat>` — "message the seller", get-or-create over the module's own
87
+ participant-pair idempotency. An opt-in antd skin at `@stapel/chat-react/default`
88
+ and a member-surface nav entry.
89
+
90
+ Two contract facts recorded rather than papered over:
91
+ `CreateConversationRequest.scope_key` is ignored by the server, so a direct
92
+ thread cannot be scoped to a listing and the pair exposes no argument that
93
+ pretends otherwise; and stapel-chat ships no `translations/` directory, so the
94
+ pair authors ru/es for the twelve error keys the module owns (the
95
+ stapel-forms/stapel_attributes precedent) while the cross-cutting keys come from
96
+ stapel-core's catalogue.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stapel contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/MODULE.md CHANGED
@@ -92,6 +92,38 @@ Unlike notifications-react, the `es` subpath here carries hand-written UI copy
92
92
  too: a marketplace's buyer-to-seller chat is the surface where a half-translated
93
93
  screen shows most.
94
94
 
95
+ ## The gate before the click, and the door beside the reason
96
+
97
+ `POST /conversations/` is `IsAuthenticated`. Until 0.3.0 `StartDirectChat`
98
+ blocked only on the two seller-shaped reasons (no seller on the listing; the
99
+ listing is your own), so a visitor pressed the button and got a 401 — a
100
+ refusal delivered at the one moment it is useless.
101
+
102
+ The mandate axis is now the FIRST arm of that `firstBlock`, read through core's
103
+ `MandateSource` seam and never derived here (a storefront's derivation is "is
104
+ there a session?", a tenant app's is `@stapel/workspaces-react`'s). Four of the
105
+ five `matchMandate` arms behave as expected; the fifth is a deliberate
106
+ exception:
107
+
108
+ | arm | outcome |
109
+ |---|---|
110
+ | `member` | available |
111
+ | `guest`, `anonymous` | blocked — `chat.start.blocked.sign_in` |
112
+ | `asking` | blocked — `chat.start.blocked.mandate_unknown` ("still asking", not "you may not") |
113
+ | `unavailable` | **available** |
114
+
115
+ `unavailable` is what core answers outside a `<MandateProvider>` too, so
116
+ refusing there would take the button away from every host that never wired the
117
+ axis — and "we could not ask" is not "you may not" (the storefront spec's own
118
+ §7.4 negative leg). If the guess is wrong, the module answers 401 exactly as it
119
+ did before.
120
+
121
+ `<StartChatButton signIn={…}>` renders the way out next to that sentence.
122
+ `SignInCta` is core's (`{href}` or `{onSignIn}`, never both) so the prop is
123
+ spelled identically in `@stapel/reviews-react` and `@stapel/listings-react`;
124
+ the LABEL is this pair's (`chat.start.sign_in`, in all three locales), because
125
+ core floors only `en` and `ru`.
126
+
95
127
  ## Extension seams (frontend-standard §7)
96
128
 
97
129
  - The client is injected via `<ChatProvider>` / core's `StapelConfigProvider`
package/README.md CHANGED
@@ -100,6 +100,27 @@ Get-or-create: a direct thread is keyed by the participant pair under a unique
100
100
  constraint, so pressing twice — or writing to the same seller about a second
101
101
  listing — lands in the same conversation.
102
102
 
103
+ ### Who may press it, and where a visitor goes
104
+
105
+ `POST /conversations/` is `IsAuthenticated`, so the first gate is the mandate
106
+ axis, read through core's `MandateSource` seam. A visitor is told to sign in
107
+ BEFORE the click instead of collecting a 401 after it — and the sentence comes
108
+ with the door:
109
+
110
+ ```tsx
111
+ <StartChatButton sellerId={sellerId} signIn={{ href: `/login?next=${here}` }} />
112
+ <StartChatButton sellerId={sellerId} signIn={{ onSignIn: () => openModal() }} />
113
+ ```
114
+
115
+ `signIn` is core's `SignInCta` — `{href}` **or** `{onSignIn}`, never both — and
116
+ it is the same prop `@stapel/reviews-react` and `@stapel/listings-react` take.
117
+ Omit it and the reason still renders, alone, which is what a host with no
118
+ sign-in route wants.
119
+
120
+ Outside a `<MandateProvider>` core answers `unresolved/unavailable`, and that
121
+ arm stays **available**: a host that never wired the axis keeps its button, and
122
+ "we could not ask" is not "you may not".
123
+
103
124
  ## The antd skin (opt-in)
104
125
 
105
126
  ```tsx
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The door beside a blocked control.
3
+ *
4
+ * `actionBlocked` ended the grey-rectangle incident by making every
5
+ * switched-off control state its reason. It did not end the next problem:
6
+ * "sign in to message the seller" is a reason whose next action is a LINK, and
7
+ * this pair rendered the sentence and stopped there — leaving the visitor to
8
+ * find the header themselves (storefront Wave D, G-3).
9
+ *
10
+ * WHERE that link goes is the container's business, never the pair's: the
11
+ * storefront's is `/login?next=<current>`, a tenant app's may be a modal. So
12
+ * the shape is core's `SignInCta` — `{href}` or `{onSignIn}`, never both — and
13
+ * the copy is this pair's, because core floors `en` and `ru` while this pair
14
+ * also ships `es`.
15
+ *
16
+ * A host that routes internally passes `onSignIn`; the `href` arm renders a
17
+ * plain anchor on purpose, because arriving at a sign-in page is one of the
18
+ * few navigations a full load costs nothing.
19
+ */
20
+ import type { ReactElement } from "react";
21
+ import type { SignInCta } from "@stapel/core";
22
+ export interface SignInLinkProps {
23
+ /** Absent: no link — a host with no sign-in route shows the reason alone. */
24
+ readonly cta: SignInCta | undefined;
25
+ readonly testId: string;
26
+ }
27
+ export declare function SignInLink(props: SignInLinkProps): ReactElement | null;
28
+ //# sourceMappingURL=SignInLink.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SignInLink.d.ts","sourceRoot":"","sources":["../../src/default/SignInLink.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAG1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG9C,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,GAAG,EAAE,SAAS,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,YAAY,GAAG,IAAI,CAsBtE"}
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Typography } from "antd";
3
+ import { useT } from "@stapel/core";
4
+ import { CHAT_I18N_KEYS } from "../i18n/keys.js";
5
+ export function SignInLink(props) {
6
+ const t = useT();
7
+ const { cta } = props;
8
+ if (cta === undefined)
9
+ return null;
10
+ // The separating space belongs HERE, not at the call site: a reason with no
11
+ // door must render as exactly its own sentence, and a `{" "}` left behind by
12
+ // an absent link is a trailing space in every caller's assertion.
13
+ return (_jsxs(_Fragment, { children: [" ", _jsx(Typography.Link, { "data-testid": props.testId, "data-analytics": "none", "data-analytics-reason": "business action \u2014 host app wraps with its own tracked()", ...(cta.href !== undefined
14
+ ? { href: cta.href }
15
+ : { onClick: cta.onSignIn }), children: t(CHAT_I18N_KEYS.startSignIn) })] }));
16
+ }
17
+ //# sourceMappingURL=SignInLink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SignInLink.js","sourceRoot":"","sources":["../../src/default/SignInLink.tsx"],"names":[],"mappings":";AAoBA,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEpC,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAQjD,MAAM,UAAU,UAAU,CAAC,KAAsB;IAC/C,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACjB,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC;IACtB,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACnC,4EAA4E;IAC5E,6EAA6E;IAC7E,kEAAkE;IAClE,OAAO,CACL,8BACG,GAAG,EACJ,KAAC,UAAU,CAAC,IAAI,mBACD,KAAK,CAAC,MAAM,oBACV,MAAM,2BACC,8DAAyD,KAC3E,CAAC,GAAG,CAAC,IAAI,KAAK,SAAS;oBACzB,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE;oBACpB,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC,YAE7B,CAAC,CAAC,cAAc,CAAC,WAAW,CAAC,GACd,IACjB,CACJ,CAAC;AACJ,CAAC"}
@@ -2,12 +2,16 @@
2
2
  * `<StartChatButton/>` — "message the seller" on a listing card, skinned.
3
3
  *
4
4
  * The control is either pressable or switched off WITH a sentence beside it
5
- * (no seller on the listing; the listing is your own). It never renders as a
6
- * grey rectangle with no explanation.
5
+ * (no seller on the listing; the listing is your own; you are not signed in).
6
+ * It never renders as a grey rectangle with no explanation — and when the host
7
+ * supplies `signIn`, the sentence comes with the door: a stated reason whose
8
+ * next action is a link the visitor cannot find on their own is only half an
9
+ * answer (storefront Wave D, G-3).
7
10
  */
8
11
  import type { ReactElement } from "react";
12
+ import type { SignInCtaProp } from "@stapel/core";
9
13
  import type { Conversation } from "../api/types.js";
10
- export interface StartChatButtonProps {
14
+ export interface StartChatButtonProps extends SignInCtaProp {
11
15
  sellerId: string | null | undefined;
12
16
  viewerId?: string | null;
13
17
  onOpened?: (conversation: Conversation) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"StartChatButton.d.ts","sourceRoot":"","sources":["../../src/default/StartChatButton.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAI1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAKpD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,IAAI,CAAC;IAChD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAsCD,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,YAAY,CAkBzE"}
1
+ {"version":3,"file":"StartChatButton.d.ts","sourceRoot":"","sources":["../../src/default/StartChatButton.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAG1C,OAAO,KAAK,EAAiC,aAAa,EAAE,MAAM,cAAc,CAAC;AACjF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAMpD,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,IAAI,CAAC;IAChD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAwCD,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,YAAY,CAmBzE"}
@@ -4,15 +4,16 @@ import { useActionGate, useErrorDisplay, useT } from "@stapel/core";
4
4
  import { StartDirectChat } from "../headless/StartDirectChat.js";
5
5
  import { CHAT_I18N_KEYS } from "../i18n/keys.js";
6
6
  import { ErrorAlert } from "./ErrorAlert.js";
7
+ import { SignInLink } from "./SignInLink.js";
7
8
  function StartChatBody(props) {
8
9
  const t = useT();
9
10
  const gate = useActionGate(props.availability);
10
11
  const errorDisplay = useErrorDisplay(CHAT_I18N_KEYS.unknownError);
11
12
  return (_jsxs(Space, { direction: "vertical", style: { width: props.block ? "100%" : undefined }, children: [_jsx(Button, { type: "primary", block: props.block ?? false, disabled: gate.disabled, loading: props.isStarting, onClick: props.start, "data-testid": "chat-start-button", "data-analytics": "none", "data-analytics-reason": "business action \u2014 host app wraps with its own tracked()", children: props.isStarting
12
13
  ? t(CHAT_I18N_KEYS.startStarting)
13
- : t(CHAT_I18N_KEYS.startButton) }), gate.reason ? (_jsx(Typography.Text, { type: "secondary", "data-testid": "chat-start-blocked", children: gate.reason })) : null, _jsx(ErrorAlert, { error: errorDisplay(props.error) })] }));
14
+ : t(CHAT_I18N_KEYS.startButton) }), gate.reason ? (_jsxs(Typography.Text, { type: "secondary", "data-testid": "chat-start-blocked", children: [gate.reason, _jsx(SignInLink, { cta: props.signIn, testId: "chat-start-sign-in" })] })) : null, _jsx(ErrorAlert, { error: errorDisplay(props.error) })] }));
14
15
  }
15
16
  export function StartChatButton(props) {
16
- return (_jsx(StartDirectChat, { sellerId: props.sellerId, ...(props.viewerId !== undefined ? { viewerId: props.viewerId } : {}), ...(props.onOpened !== undefined ? { onOpened: props.onOpened } : {}), children: ({ availability, isStarting, error, start }) => (_jsx(StartChatBody, { availability: availability, isStarting: isStarting, error: error, start: start, block: props.block })) }));
17
+ return (_jsx(StartDirectChat, { sellerId: props.sellerId, ...(props.viewerId !== undefined ? { viewerId: props.viewerId } : {}), ...(props.onOpened !== undefined ? { onOpened: props.onOpened } : {}), children: ({ availability, isStarting, error, start }) => (_jsx(StartChatBody, { availability: availability, isStarting: isStarting, error: error, start: start, block: props.block, signIn: props.signIn })) }));
17
18
  }
18
19
  //# sourceMappingURL=StartChatButton.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"StartChatButton.js","sourceRoot":"","sources":["../../src/default/StartChatButton.tsx"],"names":[],"mappings":";AAQA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAGpE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAS7C,SAAS,aAAa,CAAC,KAMtB;IACC,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACjB,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC/C,MAAM,YAAY,GAAG,eAAe,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IAClE,OAAO,CACL,MAAC,KAAK,IAAC,SAAS,EAAC,UAAU,EAAC,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAAE,aAC5E,KAAC,MAAM,IACL,IAAI,EAAC,SAAS,EACd,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,KAAK,EAC3B,QAAQ,EAAE,IAAI,CAAC,QAAQ,EACvB,OAAO,EAAE,KAAK,CAAC,UAAU,EACzB,OAAO,EAAE,KAAK,CAAC,KAAK,iBACR,mBAAmB,oBAChB,MAAM,2BACC,8DAAyD,YAE9E,KAAK,CAAC,UAAU;oBACf,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,aAAa,CAAC;oBACjC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,WAAW,CAAC,GAC1B,EACR,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CACb,KAAC,UAAU,CAAC,IAAI,IAAC,IAAI,EAAC,WAAW,iBAAa,oBAAoB,YAC/D,IAAI,CAAC,MAAM,GACI,CACnB,CAAC,CAAC,CAAC,IAAI,EACR,KAAC,UAAU,IAAC,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,GAAI,IAC1C,CACT,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAA2B;IACzD,OAAO,CACL,KAAC,eAAe,IACd,QAAQ,EAAE,KAAK,CAAC,QAAQ,KACpB,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAClE,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,YAErE,CAAC,EAAE,YAAY,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAC/C,KAAC,aAAa,IACZ,YAAY,EAAE,YAAY,EAC1B,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,KAAK,CAAC,KAAK,GAClB,CACH,GACe,CACnB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"StartChatButton.js","sourceRoot":"","sources":["../../src/default/StartChatButton.tsx"],"names":[],"mappings":";AAWA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAGpE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAS7C,SAAS,aAAa,CAAC,KAOtB;IACC,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACjB,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC/C,MAAM,YAAY,GAAG,eAAe,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IAClE,OAAO,CACL,MAAC,KAAK,IAAC,SAAS,EAAC,UAAU,EAAC,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAAE,aAC5E,KAAC,MAAM,IACL,IAAI,EAAC,SAAS,EACd,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,KAAK,EAC3B,QAAQ,EAAE,IAAI,CAAC,QAAQ,EACvB,OAAO,EAAE,KAAK,CAAC,UAAU,EACzB,OAAO,EAAE,KAAK,CAAC,KAAK,iBACR,mBAAmB,oBAChB,MAAM,2BACC,8DAAyD,YAE9E,KAAK,CAAC,UAAU;oBACf,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,aAAa,CAAC;oBACjC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,WAAW,CAAC,GAC1B,EACR,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CACb,MAAC,UAAU,CAAC,IAAI,IAAC,IAAI,EAAC,WAAW,iBAAa,oBAAoB,aAC/D,IAAI,CAAC,MAAM,EACZ,KAAC,UAAU,IAAC,GAAG,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAC,oBAAoB,GAAG,IAC7C,CACnB,CAAC,CAAC,CAAC,IAAI,EACR,KAAC,UAAU,IAAC,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,GAAI,IAC1C,CACT,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAA2B;IACzD,OAAO,CACL,KAAC,eAAe,IACd,QAAQ,EAAE,KAAK,CAAC,QAAQ,KACpB,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAClE,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,YAErE,CAAC,EAAE,YAAY,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAC/C,KAAC,aAAa,IACZ,YAAY,EAAE,YAAY,EAC1B,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,KAAK,CAAC,KAAK,EAClB,MAAM,EAAE,KAAK,CAAC,MAAM,GACpB,CACH,GACe,CACnB,CAAC;AACJ,CAAC"}
@@ -18,4 +18,6 @@ export { ConversationThreadPanel } from "./ConversationThreadPanel.js";
18
18
  export type { ConversationThreadPanelProps } from "./ConversationThreadPanel.js";
19
19
  export { StartChatButton } from "./StartChatButton.js";
20
20
  export type { StartChatButtonProps } from "./StartChatButton.js";
21
+ export { SignInLink } from "./SignInLink.js";
22
+ export type { SignInLinkProps } from "./SignInLink.js";
21
23
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,YAAY,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAC7E,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,YAAY,EAAE,4BAA4B,EAAE,MAAM,8BAA8B,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,YAAY,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAC7E,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,YAAY,EAAE,4BAA4B,EAAE,MAAM,8BAA8B,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
@@ -15,4 +15,5 @@
15
15
  export { ConversationListPanel } from "./ConversationListPanel.js";
16
16
  export { ConversationThreadPanel } from "./ConversationThreadPanel.js";
17
17
  export { StartChatButton } from "./StartChatButton.js";
18
+ export { SignInLink } from "./SignInLink.js";
18
19
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEvE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEvE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
@@ -4,11 +4,11 @@
4
4
  *
5
5
  * ── What changed since the spec ─────────────────────────────────────────────
6
6
  *
7
- * `tasks/darom-storefront-design.md` §3.6 ruled polling-by-seq for chat v1,
7
+ * The storefront spec §3.6 ruled polling-by-seq for chat v1,
8
8
  * and it was right about the fleet it surveyed: stapel-chat had the resumable
9
9
  * consumer but `routing.py` exported nothing, so no host could mount it.
10
10
  * stapel-chat 0.2.2 ships that mount (`ws/chat/<uuid:conversation_id>`) and
11
- * the darom fleet runs it. So the pair carries BOTH transports and picks at
11
+ * the client fleet runs it. So the pair carries BOTH transports and picks at
12
12
  * runtime — a deployment without sockets (WSGI, no channel layer, a
13
13
  * misconfigured proxy) is not a broken chat, it is a chat that refreshes on a
14
14
  * timer.
@@ -21,6 +21,11 @@ export interface StartDirectChatBag {
21
21
  * seller all land in the same conversation rather than fanning out into
22
22
  * near-duplicate threads.
23
23
  *
24
+ * WHO MAY PRESS IT. The write is `IsAuthenticated`, so the first gate is the
25
+ * mandate axis: a visitor is told to sign in BEFORE the click rather than
26
+ * being handed a 401 after it. `<StartChatButton signIn={…}>` puts the door
27
+ * next to that sentence.
28
+ *
24
29
  * WHAT THIS BUTTON CANNOT DO, AND WHY IT SAYS SO HERE. It cannot open a
25
30
  * thread ABOUT a listing. The thread is keyed by the pair of people, and
26
31
  * `CreateConversationRequest.scope_key` — which looks like the place to put a
@@ -1 +1 @@
1
- {"version":3,"file":"StartDirectChat.d.ts","sourceRoot":"","sources":["../../src/headless/StartDirectChat.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAIpD,mDAAmD;AACnD,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,EAAE,kBAAkB,CAAC;IAC1C,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,+BAA+B;IAC/B,QAAQ,CAAC,YAAY,EAAE,YAAY,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,KAAK,IAAI,IAAI,CAAC;CACf;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACpC,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,IAAI,CAAC;IAChD,QAAQ,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,SAAS,CAAC;CAClD,GAAG,SAAS,CA8BZ"}
1
+ {"version":3,"file":"StartDirectChat.d.ts","sourceRoot":"","sources":["../../src/headless/StartDirectChat.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAQvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAIpD,mDAAmD;AACnD,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,EAAE,kBAAkB,CAAC;IAC1C,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,+BAA+B;IAC/B,QAAQ,CAAC,YAAY,EAAE,YAAY,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,KAAK,IAAI,IAAI,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACpC,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,IAAI,CAAC;IAChD,QAAQ,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,SAAS,CAAC;CAClD,GAAG,SAAS,CAsDZ"}
@@ -1,5 +1,5 @@
1
1
  import { useCallback } from "react";
2
- import { actionAvailable, actionBlocked, firstBlock } from "@stapel/core";
2
+ import { actionAvailable, actionBlocked, firstBlock, matchMandate, useMandate, } from "@stapel/core";
3
3
  import { useStartDirectChat } from "../model/mutations.js";
4
4
  import { CHAT_I18N_KEYS } from "../i18n/keys.js";
5
5
  /**
@@ -12,6 +12,11 @@ import { CHAT_I18N_KEYS } from "../i18n/keys.js";
12
12
  * seller all land in the same conversation rather than fanning out into
13
13
  * near-duplicate threads.
14
14
  *
15
+ * WHO MAY PRESS IT. The write is `IsAuthenticated`, so the first gate is the
16
+ * mandate axis: a visitor is told to sign in BEFORE the click rather than
17
+ * being handed a 401 after it. `<StartChatButton signIn={…}>` puts the door
18
+ * next to that sentence.
19
+ *
15
20
  * WHAT THIS BUTTON CANNOT DO, AND WHY IT SAYS SO HERE. It cannot open a
16
21
  * thread ABOUT a listing. The thread is keyed by the pair of people, and
17
22
  * `CreateConversationRequest.scope_key` — which looks like the place to put a
@@ -22,7 +27,29 @@ import { CHAT_I18N_KEYS } from "../i18n/keys.js";
22
27
  export function StartDirectChat(props) {
23
28
  const start = useStartDirectChat();
24
29
  const { sellerId, viewerId, onOpened } = props;
25
- const availability = firstBlock(sellerId === null || sellerId === undefined || sellerId.length === 0
30
+ /**
31
+ * May this person write at all? `POST /conversations/` is
32
+ * `IsAuthenticated`, so a visitor pressing this bought a 401 — a refusal
33
+ * delivered after the click, which is the one moment it is useless. The
34
+ * axis is read through core's `MandateSource` seam, never derived here: a
35
+ * storefront's derivation is "is there a session?", a tenant app's is
36
+ * `@stapel/workspaces-react`'s.
37
+ *
38
+ * `unavailable` stays AVAILABLE, unlike the other four arms. Outside a
39
+ * `<MandateProvider>` core answers `unresolved/unavailable`, and a host that
40
+ * never wired the axis must not lose its button — "we could not ask" is not
41
+ * "you may not" (the storefront spec's own §7.4 negative leg), and if the
42
+ * guess is wrong the module answers 401 exactly as it did before.
43
+ */
44
+ const mandate = useMandate();
45
+ const mandateGate = matchMandate(mandate, {
46
+ member: () => actionAvailable(),
47
+ guest: () => actionBlocked(CHAT_I18N_KEYS.startBlockedSignIn),
48
+ anonymous: () => actionBlocked(CHAT_I18N_KEYS.startBlockedSignIn),
49
+ asking: () => actionBlocked(CHAT_I18N_KEYS.startBlockedMandateUnknown),
50
+ unavailable: () => actionAvailable(),
51
+ });
52
+ const availability = firstBlock(mandateGate, sellerId === null || sellerId === undefined || sellerId.length === 0
26
53
  ? actionBlocked(CHAT_I18N_KEYS.startBlockedUnknownSeller)
27
54
  : actionAvailable(), viewerId != null && viewerId === sellerId
28
55
  ? actionBlocked(CHAT_I18N_KEYS.startBlockedSelf)
@@ -1 +1 @@
1
- {"version":3,"file":"StartDirectChat.js","sourceRoot":"","sources":["../../src/headless/StartDirectChat.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAEpC,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAajD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,eAAe,CAAC,KAQ/B;IACC,MAAM,KAAK,GAAG,kBAAkB,EAAE,CAAC;IACnC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAE/C,MAAM,YAAY,GAAuB,UAAU,CACjD,QAAQ,KAAK,IAAI,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAClE,CAAC,CAAC,aAAa,CAAC,cAAc,CAAC,yBAAyB,CAAC;QACzD,CAAC,CAAC,eAAe,EAAE,EACrB,QAAQ,IAAI,IAAI,IAAI,QAAQ,KAAK,QAAQ;QACvC,CAAC,CAAC,aAAa,CAAC,cAAc,CAAC,gBAAgB,CAAC;QAChD,CAAC,CAAC,eAAe,EAAE,CACtB,CAAC;IAEF,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IACjD,MAAM,OAAO,GAAG,WAAW,CAAC,GAAS,EAAE;QACrC,IAAI,CAAC,YAAY,CAAC,SAAS,IAAI,SAAS;YAAE,OAAO;QACjD,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO;QACxD,MAAM,CACJ,EAAE,MAAM,EAAE,QAAQ,EAAE,EACpB,EAAE,SAAS,EAAE,CAAC,YAAY,EAAE,EAAE,CAAC,QAAQ,EAAE,CAAC,YAAY,CAAC,EAAE,CAC1D,CAAC;IACJ,CAAC,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;IAEpE,OAAO,KAAK,CAAC,QAAQ,CAAC;QACpB,YAAY;QACZ,UAAU,EAAE,SAAS;QACrB,YAAY,EAAE,IAAI;QAClB,KAAK;QACL,KAAK,EAAE,OAAO;KACf,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"StartDirectChat.js","sourceRoot":"","sources":["../../src/headless/StartDirectChat.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAEpC,OAAO,EACL,eAAe,EACf,aAAa,EACb,UAAU,EACV,YAAY,EACZ,UAAU,GACX,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAajD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,eAAe,CAAC,KAQ/B;IACC,MAAM,KAAK,GAAG,kBAAkB,EAAE,CAAC;IACnC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAE/C;;;;;;;;;;;;;OAaG;IACH,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,WAAW,GAAG,YAAY,CAAqB,OAAO,EAAE;QAC5D,MAAM,EAAE,GAAG,EAAE,CAAC,eAAe,EAAE;QAC/B,KAAK,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,cAAc,CAAC,kBAAkB,CAAC;QAC7D,SAAS,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,cAAc,CAAC,kBAAkB,CAAC;QACjE,MAAM,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,cAAc,CAAC,0BAA0B,CAAC;QACtE,WAAW,EAAE,GAAG,EAAE,CAAC,eAAe,EAAE;KACrC,CAAC,CAAC;IAEH,MAAM,YAAY,GAAuB,UAAU,CACjD,WAAW,EACX,QAAQ,KAAK,IAAI,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAClE,CAAC,CAAC,aAAa,CAAC,cAAc,CAAC,yBAAyB,CAAC;QACzD,CAAC,CAAC,eAAe,EAAE,EACrB,QAAQ,IAAI,IAAI,IAAI,QAAQ,KAAK,QAAQ;QACvC,CAAC,CAAC,aAAa,CAAC,cAAc,CAAC,gBAAgB,CAAC;QAChD,CAAC,CAAC,eAAe,EAAE,CACtB,CAAC;IAEF,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IACjD,MAAM,OAAO,GAAG,WAAW,CAAC,GAAS,EAAE;QACrC,IAAI,CAAC,YAAY,CAAC,SAAS,IAAI,SAAS;YAAE,OAAO;QACjD,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO;QACxD,MAAM,CACJ,EAAE,MAAM,EAAE,QAAQ,EAAE,EACpB,EAAE,SAAS,EAAE,CAAC,YAAY,EAAE,EAAE,CAAC,QAAQ,EAAE,CAAC,YAAY,CAAC,EAAE,CAC1D,CAAC;IACJ,CAAC,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;IAEpE,OAAO,KAAK,CAAC,QAAQ,CAAC;QACpB,YAAY;QACZ,UAAU,EAAE,SAAS;QACrB,YAAY,EAAE,IAAI;QAClB,KAAK;QACL,KAAK,EAAE,OAAO;KACf,CAAC,CAAC;AACL,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"es.d.ts","sourceRoot":"","sources":["../../src/i18n/es.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,EAAE,cAiE9B,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAG3D"}
1
+ {"version":3,"file":"es.d.ts","sourceRoot":"","sources":["../../src/i18n/es.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,EAAE,cAoE9B,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAG3D"}
package/dist/i18n/es.js CHANGED
@@ -57,6 +57,9 @@ export const chatI18nBundleEs = {
57
57
  "chat.start.starting": "Abriendo…",
58
58
  "chat.start.blocked.self": "Este anuncio es tuyo.",
59
59
  "chat.start.blocked.unknown_seller": "Este anuncio no tiene vendedor a quien escribir.",
60
+ "chat.start.blocked.sign_in": "Inicia sesión para escribir al vendedor.",
61
+ "chat.start.blocked.mandate_unknown": "Comprobando tu sesión…",
62
+ "chat.start.sign_in": "Iniciar sesión",
60
63
  "chat.transport.live": "En directo",
61
64
  "chat.transport.polling": "Actualizando cada pocos segundos",
62
65
  "chat.transport.idle": "En pausa",
@@ -1 +1 @@
1
- {"version":3,"file":"es.js","sourceRoot":"","sources":["../../src/i18n/es.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAmB;IAC9C,oEAAoE;IACpE,GAAG,iBAAiB;IAEpB,6EAA6E;IAC7E,qCAAqC,EACnC,gEAAgE;IAClE,8BAA8B,EAAE,gDAAgD;IAChF,8BAA8B,EAC5B,4DAA4D;IAC9D,+BAA+B,EAC7B,+DAA+D;IACjE,6BAA6B,EAAE,kCAAkC;IACjE,8BAA8B,EAC5B,8DAA8D;IAChE,8BAA8B,EAC5B,kEAAkE;IACpE,4BAA4B,EAC1B,wDAAwD;IAC1D,6BAA6B,EAC3B,wDAAwD;IAC1D,gCAAgC,EAAE,oCAAoC;IACtE,uCAAuC,EAAE,4BAA4B;IACrE,iCAAiC,EAAE,+CAA+C;IAElF,mEAAmE;IACnE,oBAAoB,EAAE,yCAAyC;IAE/D,iBAAiB,EAAE,UAAU;IAC7B,iBAAiB,EAAE,gCAAgC;IACnD,mBAAmB,EAAE,0BAA0B;IAC/C,qBAAqB,EAAE,YAAY;IACnC,eAAe,EAAE,eAAe;IAChC,iBAAiB,EAAE,YAAY;IAC/B,kBAAkB,EAAE,kBAAkB;IACtC,gBAAgB,EAAE,OAAO;IAEzB,kBAAkB,EAAE,iBAAiB;IACrC,iBAAiB,EAAE,OAAO;IAC1B,mBAAmB,EAAE,SAAS;IAE9B,qBAAqB,EAAE,oBAAoB;IAC3C,mBAAmB,EAAE,kCAAkC;IACvD,mBAAmB,EAAE,YAAY;IACjC,wBAAwB,EAAE,yBAAyB;IACnD,uBAAuB,EAAE,0CAA0C;IACnE,oBAAoB,EAAE,SAAS;IAE/B,2BAA2B,EAAE,qBAAqB;IAClD,oBAAoB,EAAE,QAAQ;IAC9B,uBAAuB,EAAE,WAAW;IACpC,6BAA6B,EAAE,uBAAuB;IACtD,gCAAgC,EAC9B,qDAAqD;IAEvD,mBAAmB,EAAE,sBAAsB;IAC3C,qBAAqB,EAAE,WAAW;IAClC,yBAAyB,EAAE,uBAAuB;IAClD,mCAAmC,EAAE,kDAAkD;IAEvF,qBAAqB,EAAE,YAAY;IACnC,wBAAwB,EAAE,kCAAkC;IAC5D,qBAAqB,EAAE,UAAU;IAEjC,wBAAwB,EAAE,UAAU;CACrC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAkB;IACnD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IAC9C,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;AAChD,CAAC"}
1
+ {"version":3,"file":"es.js","sourceRoot":"","sources":["../../src/i18n/es.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAmB;IAC9C,oEAAoE;IACpE,GAAG,iBAAiB;IAEpB,6EAA6E;IAC7E,qCAAqC,EACnC,gEAAgE;IAClE,8BAA8B,EAAE,gDAAgD;IAChF,8BAA8B,EAC5B,4DAA4D;IAC9D,+BAA+B,EAC7B,+DAA+D;IACjE,6BAA6B,EAAE,kCAAkC;IACjE,8BAA8B,EAC5B,8DAA8D;IAChE,8BAA8B,EAC5B,kEAAkE;IACpE,4BAA4B,EAC1B,wDAAwD;IAC1D,6BAA6B,EAC3B,wDAAwD;IAC1D,gCAAgC,EAAE,oCAAoC;IACtE,uCAAuC,EAAE,4BAA4B;IACrE,iCAAiC,EAAE,+CAA+C;IAElF,mEAAmE;IACnE,oBAAoB,EAAE,yCAAyC;IAE/D,iBAAiB,EAAE,UAAU;IAC7B,iBAAiB,EAAE,gCAAgC;IACnD,mBAAmB,EAAE,0BAA0B;IAC/C,qBAAqB,EAAE,YAAY;IACnC,eAAe,EAAE,eAAe;IAChC,iBAAiB,EAAE,YAAY;IAC/B,kBAAkB,EAAE,kBAAkB;IACtC,gBAAgB,EAAE,OAAO;IAEzB,kBAAkB,EAAE,iBAAiB;IACrC,iBAAiB,EAAE,OAAO;IAC1B,mBAAmB,EAAE,SAAS;IAE9B,qBAAqB,EAAE,oBAAoB;IAC3C,mBAAmB,EAAE,kCAAkC;IACvD,mBAAmB,EAAE,YAAY;IACjC,wBAAwB,EAAE,yBAAyB;IACnD,uBAAuB,EAAE,0CAA0C;IACnE,oBAAoB,EAAE,SAAS;IAE/B,2BAA2B,EAAE,qBAAqB;IAClD,oBAAoB,EAAE,QAAQ;IAC9B,uBAAuB,EAAE,WAAW;IACpC,6BAA6B,EAAE,uBAAuB;IACtD,gCAAgC,EAC9B,qDAAqD;IAEvD,mBAAmB,EAAE,sBAAsB;IAC3C,qBAAqB,EAAE,WAAW;IAClC,yBAAyB,EAAE,uBAAuB;IAClD,mCAAmC,EAAE,kDAAkD;IACvF,4BAA4B,EAAE,0CAA0C;IACxE,oCAAoC,EAAE,wBAAwB;IAC9D,oBAAoB,EAAE,gBAAgB;IAEtC,qBAAqB,EAAE,YAAY;IACnC,wBAAwB,EAAE,kCAAkC;IAC5D,qBAAqB,EAAE,UAAU;IAEjC,wBAAwB,EAAE,UAAU;CACrC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAkB;IACnD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IAC9C,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;AAChD,CAAC"}
@@ -35,6 +35,12 @@ export declare const CHAT_I18N_KEYS: {
35
35
  readonly startStarting: "chat.start.starting";
36
36
  readonly startBlockedSelf: "chat.start.blocked.self";
37
37
  readonly startBlockedUnknownSeller: "chat.start.blocked.unknown_seller";
38
+ /** A visitor: the POST is `IsAuthenticated`, so say so before the click. */
39
+ readonly startBlockedSignIn: "chat.start.blocked.sign_in";
40
+ /** We have not finished asking who this is. Not "you may not". */
41
+ readonly startBlockedMandateUnknown: "chat.start.blocked.mandate_unknown";
42
+ /** The door beside a blocked "message the seller": the container says WHERE. */
43
+ readonly startSignIn: "chat.start.sign_in";
38
44
  readonly transportLive: "chat.transport.live";
39
45
  readonly transportPolling: "chat.transport.polling";
40
46
  readonly transportIdle: "chat.transport.idle";
@@ -1 +1 @@
1
- {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/i18n/keys.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG/D;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoEjB,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,OAAO,cAAc,CAAC,CAAC;AAE/E;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,cA4C9B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,SAAO,GAAG,IAAI,CAExE"}
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/i18n/keys.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG/D;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAuCzB,4EAA4E;;IAE5E,kEAAkE;;IAElE,gFAAgF;;;;;;;;;;;;;;;;;;CA+BxE,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,OAAO,cAAc,CAAC,CAAC;AAE/E;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,cA+C9B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,SAAO,GAAG,IAAI,CAExE"}
package/dist/i18n/keys.js CHANGED
@@ -41,6 +41,12 @@ export const CHAT_I18N_KEYS = {
41
41
  startStarting: "chat.start.starting",
42
42
  startBlockedSelf: "chat.start.blocked.self",
43
43
  startBlockedUnknownSeller: "chat.start.blocked.unknown_seller",
44
+ /** A visitor: the POST is `IsAuthenticated`, so say so before the click. */
45
+ startBlockedSignIn: "chat.start.blocked.sign_in",
46
+ /** We have not finished asking who this is. Not "you may not". */
47
+ startBlockedMandateUnknown: "chat.start.blocked.mandate_unknown",
48
+ /** The door beside a blocked "message the seller": the container says WHERE. */
49
+ startSignIn: "chat.start.sign_in",
44
50
  // Transport (the seam is invisible to the UI's BEHAVIOUR, but a person may
45
51
  // still be told whether the thread is live or on a timer)
46
52
  transportLive: "chat.transport.live",
@@ -106,6 +112,9 @@ export const chatI18nBundleEn = {
106
112
  "chat.start.starting": "Opening…",
107
113
  "chat.start.blocked.self": "This is your own listing.",
108
114
  "chat.start.blocked.unknown_seller": "This listing has no seller to write to.",
115
+ "chat.start.blocked.sign_in": "Sign in to message the seller.",
116
+ "chat.start.blocked.mandate_unknown": "Checking your session…",
117
+ "chat.start.sign_in": "Sign in",
109
118
  "chat.transport.live": "Live",
110
119
  "chat.transport.polling": "Refreshing every few seconds",
111
120
  "chat.transport.idle": "Paused",
@@ -1 +1 @@
1
- {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/i18n/keys.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/i18n/keys.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ru.d.ts","sourceRoot":"","sources":["../../src/i18n/ru.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,gBAAgB,EAAE,cA+D9B,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAG3D"}
1
+ {"version":3,"file":"ru.d.ts","sourceRoot":"","sources":["../../src/i18n/ru.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,gBAAgB,EAAE,cAkE9B,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAG3D"}
package/dist/i18n/ru.js CHANGED
@@ -61,6 +61,9 @@ export const chatI18nBundleRu = {
61
61
  "chat.start.starting": "Открываем…",
62
62
  "chat.start.blocked.self": "Это ваше собственное объявление.",
63
63
  "chat.start.blocked.unknown_seller": "У этого объявления не указан продавец.",
64
+ "chat.start.blocked.sign_in": "Войдите, чтобы написать продавцу.",
65
+ "chat.start.blocked.mandate_unknown": "Проверяем вашу сессию…",
66
+ "chat.start.sign_in": "Войти",
64
67
  "chat.transport.live": "На связи",
65
68
  "chat.transport.polling": "Обновляется каждые несколько секунд",
66
69
  "chat.transport.idle": "Приостановлено",
@@ -1 +1 @@
1
- {"version":3,"file":"ru.js","sourceRoot":"","sources":["../../src/i18n/ru.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAmB;IAC9C,oEAAoE;IACpE,GAAG,iBAAiB;IAEpB,6EAA6E;IAC7E,qCAAqC,EAAE,qCAAqC;IAC5E,8BAA8B,EAAE,uCAAuC;IACvE,8BAA8B,EAC5B,yDAAyD;IAC3D,+BAA+B,EAC7B,sDAAsD;IACxD,6BAA6B,EAAE,yBAAyB;IACxD,8BAA8B,EAC5B,yDAAyD;IAC3D,8BAA8B,EAAE,4CAA4C;IAC5E,4BAA4B,EAC1B,wDAAwD;IAC1D,6BAA6B,EAC3B,kDAAkD;IACpD,gCAAgC,EAAE,8BAA8B;IAChE,uCAAuC,EAAE,kBAAkB;IAC3D,iCAAiC,EAAE,kCAAkC;IAErE,mEAAmE;IACnE,oBAAoB,EAAE,0CAA0C;IAEhE,iBAAiB,EAAE,WAAW;IAC9B,iBAAiB,EAAE,oBAAoB;IACvC,mBAAmB,EAAE,oBAAoB;IACzC,qBAAqB,EAAE,cAAc;IACrC,eAAe,EAAE,kBAAkB;IACnC,iBAAiB,EAAE,WAAW;IAC9B,kBAAkB,EAAE,wBAAwB;IAC5C,gBAAgB,EAAE,SAAS;IAE3B,kBAAkB,EAAE,kBAAkB;IACtC,iBAAiB,EAAE,kBAAkB;IACrC,mBAAmB,EAAE,WAAW;IAEhC,qBAAqB,EAAE,qBAAqB;IAC5C,mBAAmB,EAAE,sCAAsC;IAC3D,mBAAmB,EAAE,WAAW;IAChC,wBAAwB,EAAE,uBAAuB;IACjD,uBAAuB,EAAE,uBAAuB;IAChD,oBAAoB,EAAE,qBAAqB;IAE3C,2BAA2B,EAAE,qBAAqB;IAClD,oBAAoB,EAAE,WAAW;IACjC,uBAAuB,EAAE,WAAW;IACpC,6BAA6B,EAAE,yBAAyB;IACxD,gCAAgC,EAC9B,iDAAiD;IAEnD,mBAAmB,EAAE,mBAAmB;IACxC,qBAAqB,EAAE,YAAY;IACnC,yBAAyB,EAAE,kCAAkC;IAC7D,mCAAmC,EAAE,wCAAwC;IAE7E,qBAAqB,EAAE,UAAU;IACjC,wBAAwB,EAAE,qCAAqC;IAC/D,qBAAqB,EAAE,gBAAgB;IAEvC,wBAAwB,EAAE,WAAW;CACtC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAkB;IACnD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IAC9C,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;AAChD,CAAC"}
1
+ {"version":3,"file":"ru.js","sourceRoot":"","sources":["../../src/i18n/ru.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAmB;IAC9C,oEAAoE;IACpE,GAAG,iBAAiB;IAEpB,6EAA6E;IAC7E,qCAAqC,EAAE,qCAAqC;IAC5E,8BAA8B,EAAE,uCAAuC;IACvE,8BAA8B,EAC5B,yDAAyD;IAC3D,+BAA+B,EAC7B,sDAAsD;IACxD,6BAA6B,EAAE,yBAAyB;IACxD,8BAA8B,EAC5B,yDAAyD;IAC3D,8BAA8B,EAAE,4CAA4C;IAC5E,4BAA4B,EAC1B,wDAAwD;IAC1D,6BAA6B,EAC3B,kDAAkD;IACpD,gCAAgC,EAAE,8BAA8B;IAChE,uCAAuC,EAAE,kBAAkB;IAC3D,iCAAiC,EAAE,kCAAkC;IAErE,mEAAmE;IACnE,oBAAoB,EAAE,0CAA0C;IAEhE,iBAAiB,EAAE,WAAW;IAC9B,iBAAiB,EAAE,oBAAoB;IACvC,mBAAmB,EAAE,oBAAoB;IACzC,qBAAqB,EAAE,cAAc;IACrC,eAAe,EAAE,kBAAkB;IACnC,iBAAiB,EAAE,WAAW;IAC9B,kBAAkB,EAAE,wBAAwB;IAC5C,gBAAgB,EAAE,SAAS;IAE3B,kBAAkB,EAAE,kBAAkB;IACtC,iBAAiB,EAAE,kBAAkB;IACrC,mBAAmB,EAAE,WAAW;IAEhC,qBAAqB,EAAE,qBAAqB;IAC5C,mBAAmB,EAAE,sCAAsC;IAC3D,mBAAmB,EAAE,WAAW;IAChC,wBAAwB,EAAE,uBAAuB;IACjD,uBAAuB,EAAE,uBAAuB;IAChD,oBAAoB,EAAE,qBAAqB;IAE3C,2BAA2B,EAAE,qBAAqB;IAClD,oBAAoB,EAAE,WAAW;IACjC,uBAAuB,EAAE,WAAW;IACpC,6BAA6B,EAAE,yBAAyB;IACxD,gCAAgC,EAC9B,iDAAiD;IAEnD,mBAAmB,EAAE,mBAAmB;IACxC,qBAAqB,EAAE,YAAY;IACnC,yBAAyB,EAAE,kCAAkC;IAC7D,mCAAmC,EAAE,wCAAwC;IAC7E,4BAA4B,EAAE,mCAAmC;IACjE,oCAAoC,EAAE,wBAAwB;IAC9D,oBAAoB,EAAE,OAAO;IAE7B,qBAAqB,EAAE,UAAU;IACjC,wBAAwB,EAAE,qCAAqC;IAC/D,qBAAqB,EAAE,gBAAgB;IAEvC,wBAAwB,EAAE,WAAW;CACtC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAkB;IACnD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IAC9C,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;AAChD,CAAC"}
@@ -10,7 +10,7 @@ import type { ChatReconnectOptions, ChatWebSocketFactory } from "../realtime/cha
10
10
  export interface ChatRealtimeOptions {
11
11
  /**
12
12
  * Where this deployment mounts `stapel_chat.routing.websocket_urlpatterns`,
13
- * e.g. `wss://darom.example/ws/chat/`. Omit to derive it from `baseUrl`
13
+ * e.g. `wss://shop.example/ws/chat/`. Omit to derive it from `baseUrl`
14
14
  * (the canonical `/ws/chat/` at the API's own origin). Pass `null` to turn
15
15
  * the socket transport OFF explicitly — a host that knows its backend runs
16
16
  * under WSGI says so here instead of letting every tab fail a handshake
package/llms.txt CHANGED
@@ -1,4 +1,4 @@
1
- # @stapel/chat-react 0.1.0
1
+ # @stapel/chat-react 0.3.0
2
2
 
3
3
  Headless React flow pair for stapel-chat (contract >=0.2 <0.3) — business + state, zero visual opinion.
4
4
  Built on @stapel/core: typed client + StapelApiError envelope, auth token refresh,
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$generated": "by scripts/gen-manifest.mjs — do not edit; drift-gated (pnpm gen:manifest:check)",
3
3
  "package": "@stapel/chat-react",
4
- "version": "0.1.0",
4
+ "version": "0.3.0",
5
5
  "backend": {
6
6
  "module": "stapel-chat",
7
7
  "contract": ">=0.2 <0.3"
@@ -528,9 +528,12 @@
528
528
  "chat.list.title",
529
529
  "chat.list.unread",
530
530
  "chat.nav.conversations",
531
+ "chat.start.blocked.mandate_unknown",
531
532
  "chat.start.blocked.self",
533
+ "chat.start.blocked.sign_in",
532
534
  "chat.start.blocked.unknown_seller",
533
535
  "chat.start.button",
536
+ "chat.start.sign_in",
534
537
  "chat.start.starting",
535
538
  "chat.thread.beginning",
536
539
  "chat.thread.empty",
package/nav-manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "@stapel/chat-react",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "entries": [
5
5
  {
6
6
  "id": "chat.conversations",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stapel/chat-react",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Headless React pair for stapel-chat: typed API client, TanStack Query hooks, a seq-ordered thread store, and ONE transport seam behind which the module's own resumable WebSocket protocol (hello/welcome/replay/replay_done/message/error) and seq-anchored polling are interchangeable — the UI never knows which is live. Conversation list with unread badges, thread replay + live tail, REST send, direct-thread idempotency for \"message the seller\", monotonic read markers. Zero visual opinion in the main entry; an opt-in /default subpath ships the antd skin.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -47,13 +47,6 @@
47
47
  "nav-manifest.json",
48
48
  "llms.txt"
49
49
  ],
50
- "scripts": {
51
- "build": "tsc -p tsconfig.json",
52
- "test": "tsc -p tsconfig.demo.json && vitest run --exclude test/prodBundlePurity.test.ts",
53
- "test:pack": "vitest run test/prodBundlePurity.test.ts",
54
- "lint": "eslint .",
55
- "size": "size-limit"
56
- },
57
50
  "size-limit": [
58
51
  {
59
52
  "name": "index — headless pair (client + hooks + thread store + transport seam)",
@@ -77,7 +70,7 @@
77
70
  }
78
71
  ],
79
72
  "peerDependencies": {
80
- "@stapel/core": ">=0.14.0 <1.0.0",
73
+ "@stapel/core": ">=0.15.0 <1.0.0",
81
74
  "@tanstack/react-query": "^5.0.0",
82
75
  "antd": ">=5.20.0 <7",
83
76
  "react": ">=19"
@@ -89,9 +82,6 @@
89
82
  },
90
83
  "devDependencies": {
91
84
  "@size-limit/preset-small-lib": "^11.2.0",
92
- "@stapel/core": "workspace:^",
93
- "@stapel/showcase": "workspace:^",
94
- "@stapel/tokens": "workspace:^",
95
85
  "@tanstack/react-query": "^5.81.0",
96
86
  "@testing-library/react": "^16.3.0",
97
87
  "@types/react": "^19.1.0",
@@ -103,12 +93,22 @@
103
93
  "react-dom": "^19.1.0",
104
94
  "size-limit": "^11.2.0",
105
95
  "typescript": "^5.8.3",
106
- "vitest": "^3.2.4"
96
+ "vitest": "^3.2.4",
97
+ "@stapel/core": "^0.16.0",
98
+ "@stapel/showcase": "^0.2.0",
99
+ "@stapel/tokens": "^0.5.0"
107
100
  },
108
101
  "engines": {
109
102
  "node": ">=22"
110
103
  },
111
104
  "publishConfig": {
112
105
  "access": "public"
106
+ },
107
+ "scripts": {
108
+ "build": "tsc -p tsconfig.json",
109
+ "test": "tsc -p tsconfig.demo.json && vitest run --exclude test/prodBundlePurity.test.ts",
110
+ "test:pack": "vitest run test/prodBundlePurity.test.ts",
111
+ "lint": "eslint .",
112
+ "size": "size-limit"
113
113
  }
114
- }
114
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$generated": "by scripts/gen-events.mjs — do not edit; drift-gated (pnpm gen:events:check)",
3
3
  "package": "@stapel/chat-react",
4
- "version": "0.1.0",
4
+ "version": "0.3.0",
5
5
  "defined": [],
6
6
  "flows": []
7
7
  }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The door beside a blocked control.
3
+ *
4
+ * `actionBlocked` ended the grey-rectangle incident by making every
5
+ * switched-off control state its reason. It did not end the next problem:
6
+ * "sign in to message the seller" is a reason whose next action is a LINK, and
7
+ * this pair rendered the sentence and stopped there — leaving the visitor to
8
+ * find the header themselves (storefront Wave D, G-3).
9
+ *
10
+ * WHERE that link goes is the container's business, never the pair's: the
11
+ * storefront's is `/login?next=<current>`, a tenant app's may be a modal. So
12
+ * the shape is core's `SignInCta` — `{href}` or `{onSignIn}`, never both — and
13
+ * the copy is this pair's, because core floors `en` and `ru` while this pair
14
+ * also ships `es`.
15
+ *
16
+ * A host that routes internally passes `onSignIn`; the `href` arm renders a
17
+ * plain anchor on purpose, because arriving at a sign-in page is one of the
18
+ * few navigations a full load costs nothing.
19
+ */
20
+ import type { ReactElement } from "react";
21
+ import { Typography } from "antd";
22
+ import { useT } from "@stapel/core";
23
+ import type { SignInCta } from "@stapel/core";
24
+ import { CHAT_I18N_KEYS } from "../i18n/keys.js";
25
+
26
+ export interface SignInLinkProps {
27
+ /** Absent: no link — a host with no sign-in route shows the reason alone. */
28
+ readonly cta: SignInCta | undefined;
29
+ readonly testId: string;
30
+ }
31
+
32
+ export function SignInLink(props: SignInLinkProps): ReactElement | null {
33
+ const t = useT();
34
+ const { cta } = props;
35
+ if (cta === undefined) return null;
36
+ // The separating space belongs HERE, not at the call site: a reason with no
37
+ // door must render as exactly its own sentence, and a `{" "}` left behind by
38
+ // an absent link is a trailing space in every caller's assertion.
39
+ return (
40
+ <>
41
+ {" "}
42
+ <Typography.Link
43
+ data-testid={props.testId}
44
+ data-analytics="none"
45
+ data-analytics-reason="business action — host app wraps with its own tracked()"
46
+ {...(cta.href !== undefined
47
+ ? { href: cta.href }
48
+ : { onClick: cta.onSignIn })}
49
+ >
50
+ {t(CHAT_I18N_KEYS.startSignIn)}
51
+ </Typography.Link>
52
+ </>
53
+ );
54
+ }
@@ -2,19 +2,23 @@
2
2
  * `<StartChatButton/>` — "message the seller" on a listing card, skinned.
3
3
  *
4
4
  * The control is either pressable or switched off WITH a sentence beside it
5
- * (no seller on the listing; the listing is your own). It never renders as a
6
- * grey rectangle with no explanation.
5
+ * (no seller on the listing; the listing is your own; you are not signed in).
6
+ * It never renders as a grey rectangle with no explanation — and when the host
7
+ * supplies `signIn`, the sentence comes with the door: a stated reason whose
8
+ * next action is a link the visitor cannot find on their own is only half an
9
+ * answer (storefront Wave D, G-3).
7
10
  */
8
11
  import type { ReactElement } from "react";
9
12
  import { Button, Space, Typography } from "antd";
10
13
  import { useActionGate, useErrorDisplay, useT } from "@stapel/core";
11
- import type { ActionAvailability } from "@stapel/core";
14
+ import type { ActionAvailability, SignInCta, SignInCtaProp } from "@stapel/core";
12
15
  import type { Conversation } from "../api/types.js";
13
16
  import { StartDirectChat } from "../headless/StartDirectChat.js";
14
17
  import { CHAT_I18N_KEYS } from "../i18n/keys.js";
15
18
  import { ErrorAlert } from "./ErrorAlert.js";
19
+ import { SignInLink } from "./SignInLink.js";
16
20
 
17
- export interface StartChatButtonProps {
21
+ export interface StartChatButtonProps extends SignInCtaProp {
18
22
  sellerId: string | null | undefined;
19
23
  viewerId?: string | null;
20
24
  onOpened?: (conversation: Conversation) => void;
@@ -27,6 +31,7 @@ function StartChatBody(props: {
27
31
  error: unknown;
28
32
  start: () => void;
29
33
  block: boolean | undefined;
34
+ signIn: SignInCta | undefined;
30
35
  }): ReactElement {
31
36
  const t = useT();
32
37
  const gate = useActionGate(props.availability);
@@ -50,6 +55,7 @@ function StartChatBody(props: {
50
55
  {gate.reason ? (
51
56
  <Typography.Text type="secondary" data-testid="chat-start-blocked">
52
57
  {gate.reason}
58
+ <SignInLink cta={props.signIn} testId="chat-start-sign-in" />
53
59
  </Typography.Text>
54
60
  ) : null}
55
61
  <ErrorAlert error={errorDisplay(props.error)} />
@@ -71,6 +77,7 @@ export function StartChatButton(props: StartChatButtonProps): ReactElement {
71
77
  error={error}
72
78
  start={start}
73
79
  block={props.block}
80
+ signIn={props.signIn}
74
81
  />
75
82
  )}
76
83
  </StartDirectChat>
@@ -18,3 +18,5 @@ export { ConversationThreadPanel } from "./ConversationThreadPanel.js";
18
18
  export type { ConversationThreadPanelProps } from "./ConversationThreadPanel.js";
19
19
  export { StartChatButton } from "./StartChatButton.js";
20
20
  export type { StartChatButtonProps } from "./StartChatButton.js";
21
+ export { SignInLink } from "./SignInLink.js";
22
+ export type { SignInLinkProps } from "./SignInLink.js";
@@ -1,6 +1,12 @@
1
1
  import { useCallback } from "react";
2
2
  import type { ReactNode } from "react";
3
- import { actionAvailable, actionBlocked, firstBlock } from "@stapel/core";
3
+ import {
4
+ actionAvailable,
5
+ actionBlocked,
6
+ firstBlock,
7
+ matchMandate,
8
+ useMandate,
9
+ } from "@stapel/core";
4
10
  import type { ActionAvailability } from "@stapel/core";
5
11
  import type { Conversation } from "../api/types.js";
6
12
  import { useStartDirectChat } from "../model/mutations.js";
@@ -27,6 +33,11 @@ export interface StartDirectChatBag {
27
33
  * seller all land in the same conversation rather than fanning out into
28
34
  * near-duplicate threads.
29
35
  *
36
+ * WHO MAY PRESS IT. The write is `IsAuthenticated`, so the first gate is the
37
+ * mandate axis: a visitor is told to sign in BEFORE the click rather than
38
+ * being handed a 401 after it. `<StartChatButton signIn={…}>` puts the door
39
+ * next to that sentence.
40
+ *
30
41
  * WHAT THIS BUTTON CANNOT DO, AND WHY IT SAYS SO HERE. It cannot open a
31
42
  * thread ABOUT a listing. The thread is keyed by the pair of people, and
32
43
  * `CreateConversationRequest.scope_key` — which looks like the place to put a
@@ -46,7 +57,31 @@ export function StartDirectChat(props: {
46
57
  const start = useStartDirectChat();
47
58
  const { sellerId, viewerId, onOpened } = props;
48
59
 
60
+ /**
61
+ * May this person write at all? `POST /conversations/` is
62
+ * `IsAuthenticated`, so a visitor pressing this bought a 401 — a refusal
63
+ * delivered after the click, which is the one moment it is useless. The
64
+ * axis is read through core's `MandateSource` seam, never derived here: a
65
+ * storefront's derivation is "is there a session?", a tenant app's is
66
+ * `@stapel/workspaces-react`'s.
67
+ *
68
+ * `unavailable` stays AVAILABLE, unlike the other four arms. Outside a
69
+ * `<MandateProvider>` core answers `unresolved/unavailable`, and a host that
70
+ * never wired the axis must not lose its button — "we could not ask" is not
71
+ * "you may not" (the storefront spec's own §7.4 negative leg), and if the
72
+ * guess is wrong the module answers 401 exactly as it did before.
73
+ */
74
+ const mandate = useMandate();
75
+ const mandateGate = matchMandate<ActionAvailability>(mandate, {
76
+ member: () => actionAvailable(),
77
+ guest: () => actionBlocked(CHAT_I18N_KEYS.startBlockedSignIn),
78
+ anonymous: () => actionBlocked(CHAT_I18N_KEYS.startBlockedSignIn),
79
+ asking: () => actionBlocked(CHAT_I18N_KEYS.startBlockedMandateUnknown),
80
+ unavailable: () => actionAvailable(),
81
+ });
82
+
49
83
  const availability: ActionAvailability = firstBlock(
84
+ mandateGate,
50
85
  sellerId === null || sellerId === undefined || sellerId.length === 0
51
86
  ? actionBlocked(CHAT_I18N_KEYS.startBlockedUnknownSeller)
52
87
  : actionAvailable(),
package/src/i18n/es.ts CHANGED
@@ -75,6 +75,9 @@ export const chatI18nBundleEs: I18nDictionary = {
75
75
  "chat.start.starting": "Abriendo…",
76
76
  "chat.start.blocked.self": "Este anuncio es tuyo.",
77
77
  "chat.start.blocked.unknown_seller": "Este anuncio no tiene vendedor a quien escribir.",
78
+ "chat.start.blocked.sign_in": "Inicia sesión para escribir al vendedor.",
79
+ "chat.start.blocked.mandate_unknown": "Comprobando tu sesión…",
80
+ "chat.start.sign_in": "Iniciar sesión",
78
81
 
79
82
  "chat.transport.live": "En directo",
80
83
  "chat.transport.polling": "Actualizando cada pocos segundos",
package/src/i18n/keys.ts CHANGED
@@ -48,6 +48,12 @@ export const CHAT_I18N_KEYS = {
48
48
  startStarting: "chat.start.starting",
49
49
  startBlockedSelf: "chat.start.blocked.self",
50
50
  startBlockedUnknownSeller: "chat.start.blocked.unknown_seller",
51
+ /** A visitor: the POST is `IsAuthenticated`, so say so before the click. */
52
+ startBlockedSignIn: "chat.start.blocked.sign_in",
53
+ /** We have not finished asking who this is. Not "you may not". */
54
+ startBlockedMandateUnknown: "chat.start.blocked.mandate_unknown",
55
+ /** The door beside a blocked "message the seller": the container says WHERE. */
56
+ startSignIn: "chat.start.sign_in",
51
57
 
52
58
  // Transport (the seam is invisible to the UI's BEHAVIOUR, but a person may
53
59
  // still be told whether the thread is live or on a timer)
@@ -126,6 +132,9 @@ export const chatI18nBundleEn: I18nDictionary = {
126
132
  "chat.start.starting": "Opening…",
127
133
  "chat.start.blocked.self": "This is your own listing.",
128
134
  "chat.start.blocked.unknown_seller": "This listing has no seller to write to.",
135
+ "chat.start.blocked.sign_in": "Sign in to message the seller.",
136
+ "chat.start.blocked.mandate_unknown": "Checking your session…",
137
+ "chat.start.sign_in": "Sign in",
129
138
 
130
139
  "chat.transport.live": "Live",
131
140
  "chat.transport.polling": "Refreshing every few seconds",
package/src/i18n/ru.ts CHANGED
@@ -77,6 +77,9 @@ export const chatI18nBundleRu: I18nDictionary = {
77
77
  "chat.start.starting": "Открываем…",
78
78
  "chat.start.blocked.self": "Это ваше собственное объявление.",
79
79
  "chat.start.blocked.unknown_seller": "У этого объявления не указан продавец.",
80
+ "chat.start.blocked.sign_in": "Войдите, чтобы написать продавцу.",
81
+ "chat.start.blocked.mandate_unknown": "Проверяем вашу сессию…",
82
+ "chat.start.sign_in": "Войти",
80
83
 
81
84
  "chat.transport.live": "На связи",
82
85
  "chat.transport.polling": "Обновляется каждые несколько секунд",