@bitvea/feedback-toolbar 0.0.1

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 (176) hide show
  1. package/README.md +262 -0
  2. package/dist/FeedbackPanel.d.ts +117 -0
  3. package/dist/FeedbackPanel.d.ts.map +1 -0
  4. package/dist/FeedbackPanel.js +622 -0
  5. package/dist/FeedbackPanel.js.map +1 -0
  6. package/dist/FeedbackToolbar.d.ts +54 -0
  7. package/dist/FeedbackToolbar.d.ts.map +1 -0
  8. package/dist/FeedbackToolbar.js +1149 -0
  9. package/dist/FeedbackToolbar.js.map +1 -0
  10. package/dist/InboxSheet.d.ts +80 -0
  11. package/dist/InboxSheet.d.ts.map +1 -0
  12. package/dist/InboxSheet.js +580 -0
  13. package/dist/InboxSheet.js.map +1 -0
  14. package/dist/PinLayer.d.ts +59 -0
  15. package/dist/PinLayer.d.ts.map +1 -0
  16. package/dist/PinLayer.js +1702 -0
  17. package/dist/PinLayer.js.map +1 -0
  18. package/dist/RichText.d.ts +6 -0
  19. package/dist/RichText.d.ts.map +1 -0
  20. package/dist/RichText.js +135 -0
  21. package/dist/RichText.js.map +1 -0
  22. package/dist/browser/entry.d.ts +3 -0
  23. package/dist/browser/entry.d.ts.map +1 -0
  24. package/dist/browser/entry.js +13 -0
  25. package/dist/browser/entry.js.map +1 -0
  26. package/dist/browser/mount.d.ts +37 -0
  27. package/dist/browser/mount.d.ts.map +1 -0
  28. package/dist/browser/mount.js +150 -0
  29. package/dist/browser/mount.js.map +1 -0
  30. package/dist/browser/toolbar.js +38 -0
  31. package/dist/browser/toolbar.js.map +7 -0
  32. package/dist/capture.d.ts +58 -0
  33. package/dist/capture.d.ts.map +1 -0
  34. package/dist/capture.js +98 -0
  35. package/dist/capture.js.map +1 -0
  36. package/dist/cli/bin.d.ts +3 -0
  37. package/dist/cli/bin.d.ts.map +1 -0
  38. package/dist/cli/bin.js +196 -0
  39. package/dist/cli/bin.js.map +1 -0
  40. package/dist/cli/fs.d.ts +16 -0
  41. package/dist/cli/fs.d.ts.map +1 -0
  42. package/dist/cli/fs.js +33 -0
  43. package/dist/cli/fs.js.map +1 -0
  44. package/dist/cli/init.d.ts +75 -0
  45. package/dist/cli/init.d.ts.map +1 -0
  46. package/dist/cli/init.js +266 -0
  47. package/dist/cli/init.js.map +1 -0
  48. package/dist/cli/register.d.ts +55 -0
  49. package/dist/cli/register.d.ts.map +1 -0
  50. package/dist/cli/register.js +84 -0
  51. package/dist/cli/register.js.map +1 -0
  52. package/dist/cli/registration.d.ts +37 -0
  53. package/dist/cli/registration.d.ts.map +1 -0
  54. package/dist/cli/registration.js +69 -0
  55. package/dist/cli/registration.js.map +1 -0
  56. package/dist/cli/strategies.d.ts +98 -0
  57. package/dist/cli/strategies.d.ts.map +1 -0
  58. package/dist/cli/strategies.js +198 -0
  59. package/dist/cli/strategies.js.map +1 -0
  60. package/dist/client.d.ts +59 -0
  61. package/dist/client.d.ts.map +1 -0
  62. package/dist/client.js +113 -0
  63. package/dist/client.js.map +1 -0
  64. package/dist/config.d.ts +92 -0
  65. package/dist/config.d.ts.map +1 -0
  66. package/dist/config.js +65 -0
  67. package/dist/config.js.map +1 -0
  68. package/dist/deepLink.d.ts +86 -0
  69. package/dist/deepLink.d.ts.map +1 -0
  70. package/dist/deepLink.js +185 -0
  71. package/dist/deepLink.js.map +1 -0
  72. package/dist/fontFace.d.ts +70 -0
  73. package/dist/fontFace.d.ts.map +1 -0
  74. package/dist/fontFace.js +88 -0
  75. package/dist/fontFace.js.map +1 -0
  76. package/dist/gating.d.ts +32 -0
  77. package/dist/gating.d.ts.map +1 -0
  78. package/dist/gating.js +49 -0
  79. package/dist/gating.js.map +1 -0
  80. package/dist/icons.d.ts +68 -0
  81. package/dist/icons.d.ts.map +1 -0
  82. package/dist/icons.js +97 -0
  83. package/dist/icons.js.map +1 -0
  84. package/dist/index.d.ts +14 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/index.js +68 -0
  87. package/dist/index.js.map +1 -0
  88. package/dist/markdown.d.ts +59 -0
  89. package/dist/markdown.d.ts.map +1 -0
  90. package/dist/markdown.js +130 -0
  91. package/dist/markdown.js.map +1 -0
  92. package/dist/motion.d.ts +178 -0
  93. package/dist/motion.d.ts.map +1 -0
  94. package/dist/motion.js +302 -0
  95. package/dist/motion.js.map +1 -0
  96. package/dist/preferences.d.ts +71 -0
  97. package/dist/preferences.d.ts.map +1 -0
  98. package/dist/preferences.js +195 -0
  99. package/dist/preferences.js.map +1 -0
  100. package/dist/rasterise.d.ts +45 -0
  101. package/dist/rasterise.d.ts.map +1 -0
  102. package/dist/rasterise.js +490 -0
  103. package/dist/rasterise.js.map +1 -0
  104. package/dist/selector.d.ts +19 -0
  105. package/dist/selector.d.ts.map +1 -0
  106. package/dist/selector.js +142 -0
  107. package/dist/selector.js.map +1 -0
  108. package/dist/session.d.ts +19 -0
  109. package/dist/session.d.ts.map +1 -0
  110. package/dist/session.js +36 -0
  111. package/dist/session.js.map +1 -0
  112. package/dist/shortcut.d.ts +88 -0
  113. package/dist/shortcut.d.ts.map +1 -0
  114. package/dist/shortcut.js +149 -0
  115. package/dist/shortcut.js.map +1 -0
  116. package/dist/tokens.d.ts +709 -0
  117. package/dist/tokens.d.ts.map +1 -0
  118. package/dist/tokens.js +749 -0
  119. package/dist/tokens.js.map +1 -0
  120. package/dist/vendor/types/account.d.ts +26 -0
  121. package/dist/vendor/types/account.d.ts.map +1 -0
  122. package/dist/vendor/types/account.js +23 -0
  123. package/dist/vendor/types/account.js.map +1 -0
  124. package/dist/vendor/types/apiKey.d.ts +38 -0
  125. package/dist/vendor/types/apiKey.d.ts.map +1 -0
  126. package/dist/vendor/types/apiKey.js +36 -0
  127. package/dist/vendor/types/apiKey.js.map +1 -0
  128. package/dist/vendor/types/auth.d.ts +10 -0
  129. package/dist/vendor/types/auth.d.ts.map +1 -0
  130. package/dist/vendor/types/auth.js +15 -0
  131. package/dist/vendor/types/auth.js.map +1 -0
  132. package/dist/vendor/types/entity.d.ts +74 -0
  133. package/dist/vendor/types/entity.d.ts.map +1 -0
  134. package/dist/vendor/types/entity.js +111 -0
  135. package/dist/vendor/types/entity.js.map +1 -0
  136. package/dist/vendor/types/feedback.d.ts +285 -0
  137. package/dist/vendor/types/feedback.d.ts.map +1 -0
  138. package/dist/vendor/types/feedback.js +286 -0
  139. package/dist/vendor/types/feedback.js.map +1 -0
  140. package/dist/vendor/types/index.d.ts +14 -0
  141. package/dist/vendor/types/index.d.ts.map +1 -0
  142. package/dist/vendor/types/index.js +16 -0
  143. package/dist/vendor/types/index.js.map +1 -0
  144. package/dist/vendor/types/invite.d.ts +42 -0
  145. package/dist/vendor/types/invite.d.ts.map +1 -0
  146. package/dist/vendor/types/invite.js +30 -0
  147. package/dist/vendor/types/invite.js.map +1 -0
  148. package/dist/vendor/types/mcpToken.d.ts +38 -0
  149. package/dist/vendor/types/mcpToken.d.ts.map +1 -0
  150. package/dist/vendor/types/mcpToken.js +39 -0
  151. package/dist/vendor/types/mcpToken.js.map +1 -0
  152. package/dist/vendor/types/organization.d.ts +13 -0
  153. package/dist/vendor/types/organization.d.ts.map +1 -0
  154. package/dist/vendor/types/organization.js +12 -0
  155. package/dist/vendor/types/organization.js.map +1 -0
  156. package/dist/vendor/types/project.d.ts +73 -0
  157. package/dist/vendor/types/project.d.ts.map +1 -0
  158. package/dist/vendor/types/project.js +117 -0
  159. package/dist/vendor/types/project.js.map +1 -0
  160. package/dist/vendor/types/role.d.ts +12 -0
  161. package/dist/vendor/types/role.d.ts.map +1 -0
  162. package/dist/vendor/types/role.js +20 -0
  163. package/dist/vendor/types/role.js.map +1 -0
  164. package/dist/vendor/types/roleDefinition.d.ts +61 -0
  165. package/dist/vendor/types/roleDefinition.d.ts.map +1 -0
  166. package/dist/vendor/types/roleDefinition.js +45 -0
  167. package/dist/vendor/types/roleDefinition.js.map +1 -0
  168. package/dist/vendor/types/team.d.ts +25 -0
  169. package/dist/vendor/types/team.d.ts.map +1 -0
  170. package/dist/vendor/types/team.js +17 -0
  171. package/dist/vendor/types/team.js.map +1 -0
  172. package/dist/vendor/types/util.d.ts +39 -0
  173. package/dist/vendor/types/util.d.ts.map +1 -0
  174. package/dist/vendor/types/util.js +31 -0
  175. package/dist/vendor/types/util.js.map +1 -0
  176. package/package.json +59 -0
@@ -0,0 +1,198 @@
1
+ // How the provider gets into a host app, one strategy per install path.
2
+ //
3
+ // The spec asks for this explicitly: another framework, or another way of
4
+ // mounting into Next.js, should be a new strategy rather than a rewrite of the
5
+ // injector. So the orchestration in `init.ts` knows only this interface, and
6
+ // the one strategy that exists here knows only App Router layouts.
7
+ import { joinPath } from "./fs.js";
8
+ export const PACKAGE_NAME = "@bitvea/feedback-toolbar";
9
+ export const COMPONENT_NAME = "FeedbackToolbar";
10
+ /** The file the script-tag route is served as, relative to the backend URL. */
11
+ export const SCRIPT_BUNDLE_PATH = "/toolbar.js";
12
+ /**
13
+ * The environment a script-tag install declares, when the developer has not
14
+ * said otherwise.
15
+ *
16
+ * The gate treats "absent" as off, and a static HTML file cannot know which
17
+ * deployment served it - so a tag with no `data-environment` mounts nothing,
18
+ * ever. That is the correct default for a value we could only guess, and the
19
+ * wrong default for an installer whose whole job is to leave a working
20
+ * install behind: #17 asks for a toolbar that mounts on a plain page with no
21
+ * build step, and a tag the developer has to hand-edit is not that.
22
+ *
23
+ * So `init` writes an environment, and the value is a review environment
24
+ * because installing a review tool on a page is what the command means. The
25
+ * result is printed as a change and called out as a note, and `--environment`
26
+ * overrides it - including with `production`, which is how somebody turns the
27
+ * tag off without removing it.
28
+ */
29
+ export const DEFAULT_SCRIPT_ENVIRONMENT = "preview";
30
+ /** Where an App Router root layout lives, most conventional first. */
31
+ const APP_ROUTER_LAYOUTS = [
32
+ "app/layout.tsx",
33
+ "src/app/layout.tsx",
34
+ "app/layout.jsx",
35
+ "src/app/layout.jsx",
36
+ ];
37
+ export const nextAppRouterStrategy = {
38
+ id: "next-app-router",
39
+ label: "Next.js App Router",
40
+ delivery: "npm-package",
41
+ detect(fs, root) {
42
+ for (const candidate of APP_ROUTER_LAYOUTS) {
43
+ const path = joinPath(root, candidate);
44
+ if (fs.exists(path)) {
45
+ return { path, description: `root layout at ${candidate}` };
46
+ }
47
+ }
48
+ return null;
49
+ },
50
+ isInstalled(source) {
51
+ // Both halves, because a half-installed file is what a failed earlier run
52
+ // would leave and re-running should finish it rather than skip it.
53
+ return (source.includes(PACKAGE_NAME) && source.includes(`<${COMPONENT_NAME}`));
54
+ },
55
+ inject(source, context) {
56
+ let out = source;
57
+ if (!out.includes(PACKAGE_NAME)) {
58
+ out = addImport(out);
59
+ }
60
+ if (!out.includes(`<${COMPONENT_NAME}`)) {
61
+ // The API URL goes in the MARKUP, not only into an env file, and that is
62
+ // the difference between an install that works after a deploy and one
63
+ // that silently does nothing.
64
+ //
65
+ // `init` writes `NEXT_PUBLIC_FEEDBACK_TOOLBAR_API_URL` into `.env.local`,
66
+ // which create-next-app gitignores - so on the host's next deploy the
67
+ // variable does not exist, `resolveConfig` leaves `enabled` false, and
68
+ // the deployed toolbar renders nothing. The developer sees no toolbar and
69
+ // no error, and nothing in the install told them to add the variable to
70
+ // their hosting provider by hand.
71
+ //
72
+ // The value is not a secret - it is a `NEXT_PUBLIC_` URL, and the
73
+ // script-tag route already writes the same value into a page - so
74
+ // committing it is exactly right. `resolveConfig` lets a prop beat the
75
+ // environment, which is what makes this deterministic; a developer who
76
+ // wants a different backend per environment deletes the prop and sets
77
+ // the variable in their hosting provider instead.
78
+ out = insertBeforeClosingBody(out, `<${COMPONENT_NAME} apiUrl="${context.apiUrl}" />`);
79
+ }
80
+ return out;
81
+ },
82
+ };
83
+ /** Where a plain HTML entry point lives, most conventional first. */
84
+ const HTML_ENTRY_FILES = ["index.html", "public/index.html", "src/index.html"];
85
+ /**
86
+ * The `<script src>` install, for an app with no build step to add a
87
+ * dependency to. Second in the list on purpose: an App Router app usually
88
+ * also has an index.html somewhere, and the npm install is the better one.
89
+ *
90
+ * Note what the injected tag does NOT carry: a deploy key. There is no
91
+ * supported way to put one in a page, and the bundle refuses to mount when it
92
+ * finds anything that looks like one - a key in a page is a key in every
93
+ * visitor's hands, and its whole power is creating projects.
94
+ *
95
+ * It does carry an environment, and it has to. A static file cannot know which
96
+ * deployment served it and the gate treats absent as off, so a tag without one
97
+ * mounts nothing at all - which is what #17's "mounts a working toolbar on a
98
+ * plain HTML page" cannot mean. The value is written, printed as a change, and
99
+ * called out as a note; `--environment production` is how a developer turns
100
+ * the tag off without deleting it. See DEFAULT_SCRIPT_ENVIRONMENT.
101
+ */
102
+ export const htmlScriptTagStrategy = {
103
+ id: "html-script-tag",
104
+ label: "plain HTML page",
105
+ delivery: "script-tag",
106
+ detect(fs, root) {
107
+ for (const candidate of HTML_ENTRY_FILES) {
108
+ const path = joinPath(root, candidate);
109
+ if (fs.exists(path)) {
110
+ return { path, description: `HTML page at ${candidate}` };
111
+ }
112
+ }
113
+ return null;
114
+ },
115
+ isInstalled(source) {
116
+ return source.includes(SCRIPT_BUNDLE_PATH);
117
+ },
118
+ inject(source, context) {
119
+ if (!context.projectUrl) {
120
+ // Unlike the npm route, this one cannot fall back to reading the URL
121
+ // from the environment at runtime: a static page has no environment.
122
+ // Without it the toolbar would ask the backend about a project keyed on
123
+ // the empty string, so refuse rather than install something inert.
124
+ throw new InjectionError("a script-tag install needs the project's URL, and nothing here reports one.\n" +
125
+ "Re-run with --project-url https://myapp.com.");
126
+ }
127
+ const environment = context.environment ?? DEFAULT_SCRIPT_ENVIRONMENT;
128
+ return insertBeforeClosingBody(source, `<script src="${context.apiUrl}${SCRIPT_BUNDLE_PATH}" ` +
129
+ `data-api-url="${context.apiUrl}" ` +
130
+ `data-project-url="${context.projectUrl}" ` +
131
+ `data-environment="${environment}" defer></script>`);
132
+ },
133
+ };
134
+ function addImport(source) {
135
+ const importLine = `import { ${COMPONENT_NAME} } from "${PACKAGE_NAME}";\n`;
136
+ // After the last top-level import, so the file keeps its import block
137
+ // together rather than growing a stray line above a directive.
138
+ const importRe = /^import[\s\S]*?;[ \t]*$/gm;
139
+ let lastEnd = -1;
140
+ for (const match of source.matchAll(importRe)) {
141
+ lastEnd = match.index + match[0].length;
142
+ }
143
+ if (lastEnd >= 0) {
144
+ return `${source.slice(0, lastEnd)}\n${importLine.trimEnd()}${source.slice(lastEnd)}`;
145
+ }
146
+ // No imports at all: go after a leading "use client" directive if present,
147
+ // since nothing may precede one.
148
+ const directive = /^\s*("use client"|'use client');?\s*\n/.exec(source);
149
+ if (directive) {
150
+ const end = directive.index + directive[0].length;
151
+ return `${source.slice(0, end)}\n${importLine}${source.slice(end)}`;
152
+ }
153
+ return `${importLine}\n${source}`;
154
+ }
155
+ /**
156
+ * Put `markup` immediately inside the closing `</body>`.
157
+ *
158
+ * Shared by both strategies because the placement problem is the same one in
159
+ * JSX and in HTML, and the interesting case is the same too: the one-line
160
+ * `<body>…</body>` that Next's template and hand-written HTML pages both use.
161
+ */
162
+ function insertBeforeClosingBody(source, markup) {
163
+ const closingBody = source.lastIndexOf("</body>");
164
+ if (closingBody === -1) {
165
+ // Refusing beats guessing. A layout with no <body> is not an App Router
166
+ // root layout, and injecting somewhere plausible would produce a build
167
+ // failure the developer has to reverse-engineer.
168
+ throw new InjectionError("found a layout file but no <body> tag to place the toolbar in");
169
+ }
170
+ // Immediately before `</body>`, never at the start of its line: Next's own
171
+ // template ships `<body>{children}</body>` on one line, and inserting at the
172
+ // line start would put the toolbar between <html> and <body>, which is a
173
+ // broken layout rather than an install.
174
+ const lineStart = source.lastIndexOf("\n", closingBody) + 1;
175
+ const beforeOnLine = source.slice(lineStart, closingBody);
176
+ const openingBodyOnSameLine = beforeOnLine.includes("<body");
177
+ if (openingBodyOnSameLine) {
178
+ // Everything is on one line, so splice in place rather than reformat.
179
+ return `${source.slice(0, closingBody)}${markup}${source.slice(closingBody)}`;
180
+ }
181
+ // `</body>` is on its own line: take its indentation plus one step, so the
182
+ // diff reads as if a person wrote it.
183
+ const indent = beforeOnLine.match(/^\s*/)?.[0] ?? "";
184
+ const line = `${indent} ${markup}\n`;
185
+ return `${source.slice(0, lineStart)}${line}${source.slice(lineStart)}`;
186
+ }
187
+ /** A failure the developer can act on, as opposed to an internal error. */
188
+ export class InjectionError extends Error {
189
+ constructor(message) {
190
+ super(message);
191
+ this.name = "InjectionError";
192
+ }
193
+ }
194
+ export const STRATEGIES = [
195
+ nextAppRouterStrategy,
196
+ htmlScriptTagStrategy,
197
+ ];
198
+ //# sourceMappingURL=strategies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strategies.js","sourceRoot":"","sources":["../../src/cli/strategies.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ import type { FeedbackCreateInput, FeedbackItemDTO, FeedbackStatus, ToolbarSessionState } from "./vendor/types/index.js";
2
+ export interface ToolbarSession {
3
+ state: ToolbarSessionState;
4
+ user?: {
5
+ id: string;
6
+ email: string;
7
+ name: string;
8
+ };
9
+ project?: {
10
+ id: string;
11
+ name: string;
12
+ };
13
+ role?: string;
14
+ }
15
+ /** A failed call, carrying the API's machine-readable code where there is one. */
16
+ export declare class ToolbarApiError extends Error {
17
+ readonly code: string;
18
+ readonly status: number;
19
+ constructor(code: string, status: number, message?: string);
20
+ }
21
+ export declare function createToolbarClient(apiUrl: string): {
22
+ /** Who the caller is, and whether they can act on this project. */
23
+ session(projectUrl: string): Promise<ToolbarSession>;
24
+ /** Ask for a sign-in link. Always resolves, whoever the address belongs to. */
25
+ requestMagicLink(email: string, callbackUrl?: string): Promise<{
26
+ sent: true;
27
+ }>;
28
+ signOut(): Promise<{
29
+ ok: true;
30
+ }>;
31
+ submit(input: FeedbackCreateInput): Promise<FeedbackItemDTO>;
32
+ /** Feedback on one page, which is how the toolbar finds this page's pins. */
33
+ listForPage(organizationId: string, pageUrl: string, status?: FeedbackStatus): Promise<FeedbackItemDTO[]>;
34
+ /**
35
+ * Every item in the project, whatever page it was filed on, newest first.
36
+ * The inbox sheet widens to this: a reviewer working through a round of
37
+ * feedback needs to see the items waiting on the pages they have not
38
+ * opened yet, and the same route answers both because `pageUrl` is an
39
+ * optional filter rather than a separate endpoint.
40
+ *
41
+ * `limit` is passed explicitly rather than left to the server's default
42
+ * because this is the one unfiltered read the toolbar makes, against the
43
+ * one table that grows for the life of a project. Stating the bound here
44
+ * is what lets the sheet say "the newest N" instead of quietly showing a
45
+ * truncated list as if it were the whole thing.
46
+ */
47
+ listForProject(organizationId: string, limit: number, status?: FeedbackStatus): Promise<FeedbackItemDTO[]>;
48
+ /**
49
+ * Move an item between lanes. The mover is never on the wire: the API
50
+ * takes it from the session cookie and records it alongside the new
51
+ * status, so a reviewer cannot attribute their own triage to someone
52
+ * else. The re-read item comes back carrying that attribution, which is
53
+ * why the thread can render "resolved by X" straight from the response.
54
+ */
55
+ setStatus(organizationId: string, itemId: string, status: FeedbackStatus): Promise<FeedbackItemDTO>;
56
+ reply(organizationId: string, itemId: string, body: string, screenshot?: string): Promise<FeedbackItemDTO>;
57
+ };
58
+ export type ToolbarClient = ReturnType<typeof createToolbarClient>;
59
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,mBAAmB,EACnB,eAAe,EACf,cAAc,EACd,mBAAmB,EACpB,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,mBAAmB,CAAC;IAC3B,IAAI,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,OAAO,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,kFAAkF;AAClF,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM;CAM3D;AA8BD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM;IAE9C,mEAAmE;wBAC/C,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAOpD,+EAA+E;4BAEtE,MAAM,gBACC,MAAM,GACnB,OAAO,CAAC;QAAE,IAAI,EAAE,IAAI,CAAA;KAAE,CAAC;eAOf,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,CAAC;kBAMlB,mBAAmB,GAAG,OAAO,CAAC,eAAe,CAAC;IAO5D,6EAA6E;gCAE3D,MAAM,WACb,MAAM,WACN,cAAc,GACtB,OAAO,CAAC,eAAe,EAAE,CAAC;IAM7B;;;;;;;;;;;;OAYG;mCAEe,MAAM,SACf,MAAM,WACJ,cAAc,GACtB,OAAO,CAAC,eAAe,EAAE,CAAC;IAS7B;;;;;;OAMG;8BAEe,MAAM,UACd,MAAM,UACN,cAAc,GACrB,OAAO,CAAC,eAAe,CAAC;0BAST,MAAM,UACd,MAAM,QACR,MAAM,eAOC,MAAM,GAClB,OAAO,CAAC,eAAe,CAAC;EAW9B;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC"}
package/dist/client.js ADDED
@@ -0,0 +1,113 @@
1
+ /** A failed call, carrying the API's machine-readable code where there is one. */
2
+ export class ToolbarApiError extends Error {
3
+ code;
4
+ status;
5
+ constructor(code, status, message) {
6
+ super(message ?? code);
7
+ this.name = "ToolbarApiError";
8
+ this.code = code;
9
+ this.status = status;
10
+ }
11
+ }
12
+ async function request(apiUrl, path, init = {}) {
13
+ let res;
14
+ try {
15
+ res = await fetch(`${apiUrl}${path}`, {
16
+ ...init,
17
+ credentials: "include",
18
+ headers: {
19
+ ...(init.body ? { "content-type": "application/json" } : {}),
20
+ ...init.headers,
21
+ },
22
+ });
23
+ }
24
+ catch (cause) {
25
+ // A network failure and a rejected CORS preflight look the same from here.
26
+ // Both mean the same thing to a reviewer: it did not send, try again.
27
+ throw new ToolbarApiError("network_error", 0, String(cause));
28
+ }
29
+ if (!res.ok) {
30
+ const body = (await res.json().catch(() => ({})));
31
+ throw new ToolbarApiError(body.error ?? "request_failed", res.status);
32
+ }
33
+ return (await res.json());
34
+ }
35
+ export function createToolbarClient(apiUrl) {
36
+ return {
37
+ /** Who the caller is, and whether they can act on this project. */
38
+ session(projectUrl) {
39
+ return request(apiUrl, `/api/v1/toolbar-auth/session?projectUrl=${encodeURIComponent(projectUrl)}`);
40
+ },
41
+ /** Ask for a sign-in link. Always resolves, whoever the address belongs to. */
42
+ requestMagicLink(email, callbackUrl) {
43
+ return request(apiUrl, "/api/v1/toolbar-auth/magic-link", {
44
+ method: "POST",
45
+ body: JSON.stringify({ email, callbackUrl }),
46
+ });
47
+ },
48
+ signOut() {
49
+ return request(apiUrl, "/api/v1/toolbar-auth/sign-out", {
50
+ method: "POST",
51
+ });
52
+ },
53
+ submit(input) {
54
+ return request(apiUrl, "/api/v1/feedback", {
55
+ method: "POST",
56
+ body: JSON.stringify(input),
57
+ });
58
+ },
59
+ /** Feedback on one page, which is how the toolbar finds this page's pins. */
60
+ listForPage(organizationId, pageUrl, status) {
61
+ const params = new URLSearchParams({ organizationId, pageUrl });
62
+ if (status)
63
+ params.set("status", status);
64
+ return request(apiUrl, `/api/v1/feedback?${params.toString()}`);
65
+ },
66
+ /**
67
+ * Every item in the project, whatever page it was filed on, newest first.
68
+ * The inbox sheet widens to this: a reviewer working through a round of
69
+ * feedback needs to see the items waiting on the pages they have not
70
+ * opened yet, and the same route answers both because `pageUrl` is an
71
+ * optional filter rather than a separate endpoint.
72
+ *
73
+ * `limit` is passed explicitly rather than left to the server's default
74
+ * because this is the one unfiltered read the toolbar makes, against the
75
+ * one table that grows for the life of a project. Stating the bound here
76
+ * is what lets the sheet say "the newest N" instead of quietly showing a
77
+ * truncated list as if it were the whole thing.
78
+ */
79
+ listForProject(organizationId, limit, status) {
80
+ const params = new URLSearchParams({
81
+ organizationId,
82
+ limit: String(limit),
83
+ });
84
+ if (status)
85
+ params.set("status", status);
86
+ return request(apiUrl, `/api/v1/feedback?${params.toString()}`);
87
+ },
88
+ /**
89
+ * Move an item between lanes. The mover is never on the wire: the API
90
+ * takes it from the session cookie and records it alongside the new
91
+ * status, so a reviewer cannot attribute their own triage to someone
92
+ * else. The re-read item comes back carrying that attribution, which is
93
+ * why the thread can render "resolved by X" straight from the response.
94
+ */
95
+ setStatus(organizationId, itemId, status) {
96
+ return request(apiUrl, `/api/v1/feedback/${itemId}/status?organizationId=${encodeURIComponent(organizationId)}`, { method: "PATCH", body: JSON.stringify({ status }) });
97
+ },
98
+ reply(organizationId, itemId, body,
99
+ /**
100
+ * A data URL of one element the reviewer pointed at, or undefined.
101
+ * Omitted from the payload entirely when absent rather than sent as
102
+ * null: the schema makes it optional, and an explicit null would be a
103
+ * different shape for the same fact.
104
+ */
105
+ screenshot) {
106
+ return request(apiUrl, `/api/v1/feedback/${itemId}/replies?organizationId=${encodeURIComponent(organizationId)}`, {
107
+ method: "POST",
108
+ body: JSON.stringify(screenshot ? { body, screenshot } : { body }),
109
+ });
110
+ },
111
+ };
112
+ }
113
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ import { type PinShortcutSetting } from "./shortcut.js";
2
+ export interface ToolbarConfig {
3
+ /** Base URL of the BitVea Feedback backend, no trailing slash. */
4
+ apiUrl: string;
5
+ /**
6
+ * Where the toolbar's own static assets - the two font files - are served
7
+ * from, no trailing slash. Defaults to `apiUrl`, which is right for every
8
+ * real install: the backend that serves the API serves `/fonts` too.
9
+ *
10
+ * It is a separate field because the two are not the same question. A
11
+ * harness can legitimately point the API at a host that does not exist and
12
+ * answer every call locally, which is exactly what the preview route does -
13
+ * and welding the font URL to `apiUrl` made the fonts 404 there, so the one
14
+ * page built to review the toolbar was the one page that rendered it in the
15
+ * wrong typeface.
16
+ */
17
+ assetOrigin: string;
18
+ /** The project this deployment reports against, its normalised origin. */
19
+ projectUrl: string;
20
+ /** "preview", "development" or "production". */
21
+ environment: string;
22
+ /** Git branch, when the build knows it. */
23
+ gitBranch?: string | undefined;
24
+ /** Git commit SHA, when the build knows it. */
25
+ gitCommit?: string | undefined;
26
+ /** Whether the toolbar renders and talks to the network at all. */
27
+ enabled: boolean;
28
+ /**
29
+ * The chord that arms pinning, or `null` to claim no key at all.
30
+ *
31
+ * THIS IS THE HOST'S REMEDY, not a preference. The toolbar listens for the
32
+ * chord in the capture phase and swallows it when it acts, so a host
33
+ * application that binds the same keys loses its own binding silently - and
34
+ * the default chord is known to collide (`Cmd+Space` is Spotlight on macOS;
35
+ * `Ctrl+Space` is commonly an input method's). Claiming a key inside
36
+ * somebody else's application is only defensible while there is a way out
37
+ * of it, so this field is that way out, and `null` is honoured by
38
+ * registering no listener rather than by declining inside one.
39
+ */
40
+ pinShortcut: PinShortcutSetting;
41
+ }
42
+ /** Props a host app may pass to override what the environment reports. */
43
+ export interface ToolbarConfigOverrides {
44
+ apiUrl?: string;
45
+ assetOrigin?: string;
46
+ projectUrl?: string;
47
+ environment?: string;
48
+ gitBranch?: string;
49
+ gitCommit?: string;
50
+ /** Force the gate one way for a test harness. Not for production use. */
51
+ enabled?: boolean;
52
+ /**
53
+ * Replace the pinning chord, or pass `null` to switch it off.
54
+ *
55
+ * Omitted means the default. `null` is a deliberate value rather than a
56
+ * missing one, which is why this is not `ShortcutChord | undefined`: a host
57
+ * has to be able to say "no key" and be heard.
58
+ */
59
+ pinShortcut?: PinShortcutSetting;
60
+ }
61
+ export declare const API_URL_ENV_VAR = "NEXT_PUBLIC_FEEDBACK_TOOLBAR_API_URL";
62
+ /**
63
+ * `NEXT_PUBLIC_` env names Vercel populates in the client bundle. They are
64
+ * read through a bag rather than off `process.env` directly so this stays a
65
+ * pure function, and because a bundler needs literal member access to inline
66
+ * them, which the caller does.
67
+ */
68
+ export declare const VERCEL_ENV_VARS: {
69
+ readonly env: "NEXT_PUBLIC_VERCEL_ENV";
70
+ readonly branch: "NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF";
71
+ readonly commit: "NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA";
72
+ /**
73
+ * The project's stable production hostname, and the identity a Project row
74
+ * is keyed by. Preferred over the per-deployment URL below because a
75
+ * preview URL changes on every commit, and keying on it would create a new
76
+ * project per deploy instead of collecting a project's feedback in one
77
+ * place. The installer registers the same value, so the two agree.
78
+ */
79
+ readonly productionUrl: "NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL";
80
+ /** This deployment's own URL. The fallback, and what a local build has. */
81
+ readonly url: "NEXT_PUBLIC_VERCEL_URL";
82
+ };
83
+ /**
84
+ * Resolve the running configuration. Overrides win over the environment, and
85
+ * the environment wins over nothing.
86
+ *
87
+ * `enabled` folds in one extra condition beyond the environment gate: without
88
+ * an API URL the toolbar has nowhere to send anything, so it stays dark rather
89
+ * than rendering a button that fails on click.
90
+ */
91
+ export declare function resolveConfig(env: Record<string, string | undefined>, overrides?: ToolbarConfigOverrides): ToolbarConfig;
92
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAOA,OAAO,EAAwB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE3E,MAAM,WAAW,aAAa;IAC5B,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;;;;OAWG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,mEAAmE;IACnE,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;;;;;;;OAWG;IACH,WAAW,EAAE,kBAAkB,CAAC;CACjC;AAED,0EAA0E;AAC1E,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,kBAAkB,CAAC;CAClC;AAED,eAAO,MAAM,eAAe,yCAAyC,CAAC;AAEtE;;;;;GAKG;AACH,eAAO,MAAM,eAAe;;;;IAI1B;;;;;;OAMG;;IAEH,2EAA2E;;CAEnE,CAAC;AAMX;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EACvC,SAAS,GAAE,sBAA2B,GACrC,aAAa,CA8Bf"}
package/dist/config.js ADDED
@@ -0,0 +1,65 @@
1
+ // The toolbar's configuration, and where each field comes from.
2
+ //
3
+ // Defaults are read from the environment Vercel already populates, so a
4
+ // correct install needs no config object at all; every field can still be
5
+ // overridden by a prop for the cases the environment cannot describe (a local
6
+ // harness, a preview pointed at a different backend).
7
+ import { isToolbarEnabledFromEnv } from "./gating.js";
8
+ import { DEFAULT_PIN_SHORTCUT } from "./shortcut.js";
9
+ export const API_URL_ENV_VAR = "NEXT_PUBLIC_FEEDBACK_TOOLBAR_API_URL";
10
+ /**
11
+ * `NEXT_PUBLIC_` env names Vercel populates in the client bundle. They are
12
+ * read through a bag rather than off `process.env` directly so this stays a
13
+ * pure function, and because a bundler needs literal member access to inline
14
+ * them, which the caller does.
15
+ */
16
+ export const VERCEL_ENV_VARS = {
17
+ env: "NEXT_PUBLIC_VERCEL_ENV",
18
+ branch: "NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF",
19
+ commit: "NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA",
20
+ /**
21
+ * The project's stable production hostname, and the identity a Project row
22
+ * is keyed by. Preferred over the per-deployment URL below because a
23
+ * preview URL changes on every commit, and keying on it would create a new
24
+ * project per deploy instead of collecting a project's feedback in one
25
+ * place. The installer registers the same value, so the two agree.
26
+ */
27
+ productionUrl: "NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL",
28
+ /** This deployment's own URL. The fallback, and what a local build has. */
29
+ url: "NEXT_PUBLIC_VERCEL_URL",
30
+ };
31
+ function trimSlashes(value) {
32
+ return value.replace(/\/+$/, "");
33
+ }
34
+ /**
35
+ * Resolve the running configuration. Overrides win over the environment, and
36
+ * the environment wins over nothing.
37
+ *
38
+ * `enabled` folds in one extra condition beyond the environment gate: without
39
+ * an API URL the toolbar has nowhere to send anything, so it stays dark rather
40
+ * than rendering a button that fails on click.
41
+ */
42
+ export function resolveConfig(env, overrides = {}) {
43
+ const apiUrl = trimSlashes(overrides.apiUrl ?? env[API_URL_ENV_VAR] ?? "");
44
+ const assetOrigin = trimSlashes(overrides.assetOrigin ?? apiUrl);
45
+ const vercelHost = env[VERCEL_ENV_VARS.productionUrl] ?? env[VERCEL_ENV_VARS.url];
46
+ const projectUrl = trimSlashes(overrides.projectUrl ?? (vercelHost ? `https://${vercelHost}` : ""));
47
+ const environment = overrides.environment ?? env[VERCEL_ENV_VARS.env] ?? "unknown";
48
+ const gatePasses = isToolbarEnabledFromEnv(env);
49
+ const enabled = (overrides.enabled ?? gatePasses) && apiUrl.length > 0;
50
+ return {
51
+ apiUrl,
52
+ assetOrigin,
53
+ projectUrl,
54
+ environment,
55
+ gitBranch: overrides.gitBranch ?? env[VERCEL_ENV_VARS.branch],
56
+ gitCommit: overrides.gitCommit ?? env[VERCEL_ENV_VARS.commit],
57
+ enabled,
58
+ // `??` and not `||`, because `null` is the host saying "no key" and must
59
+ // survive; only an omitted override falls back to the default.
60
+ pinShortcut: overrides.pinShortcut === undefined
61
+ ? DEFAULT_PIN_SHORTCUT
62
+ : overrides.pinShortcut,
63
+ };
64
+ }
65
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,EAAE;AACF,wEAAwE;AACxE,0EAA0E;AAC1E,8EAA8E;AAC9E,sDAAsD;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAA2B,MAAM,YAAY,CAAC;AA+D3E,MAAM,CAAC,MAAM,eAAe,GAAG,sCAAsC,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,GAAG,EAAE,wBAAwB;IAC7B,MAAM,EAAE,mCAAmC;IAC3C,MAAM,EAAE,mCAAmC;IAC3C;;;;;;OAMG;IACH,aAAa,EAAE,2CAA2C;IAC1D,2EAA2E;IAC3E,GAAG,EAAE,wBAAwB;CACrB,CAAC;AAEX,SAAS,WAAW,CAAC,KAAa;IAChC,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAC3B,GAAuC,EACvC,YAAoC,EAAE;IAEtC,MAAM,MAAM,GAAG,WAAW,CAAC,SAAS,CAAC,MAAM,IAAI,GAAG,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3E,MAAM,WAAW,GAAG,WAAW,CAAC,SAAS,CAAC,WAAW,IAAI,MAAM,CAAC,CAAC;IACjE,MAAM,UAAU,GACd,GAAG,CAAC,eAAe,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;IACjE,MAAM,UAAU,GAAG,WAAW,CAC5B,SAAS,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CACpE,CAAC;IACF,MAAM,WAAW,GACf,SAAS,CAAC,WAAW,IAAI,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC;IAEjE,MAAM,UAAU,GAAG,uBAAuB,CAAC,GAAG,CAAC,CAAC;IAChD,MAAM,OAAO,GACX,CAAC,SAAS,CAAC,OAAO,IAAI,UAAU,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAEzD,OAAO;QACL,MAAM;QACN,WAAW;QACX,UAAU;QACV,WAAW;QACX,SAAS,EAAE,SAAS,CAAC,SAAS,IAAI,GAAG,CAAC,eAAe,CAAC,MAAM,CAAC;QAC7D,SAAS,EAAE,SAAS,CAAC,SAAS,IAAI,GAAG,CAAC,eAAe,CAAC,MAAM,CAAC;QAC7D,OAAO;QACP,yEAAyE;QACzE,+DAA+D;QAC/D,WAAW,EACT,SAAS,CAAC,WAAW,KAAK,SAAS;YACjC,CAAC,CAAC,oBAAoB;YACtB,CAAC,CAAC,SAAS,CAAC,WAAW;KAC5B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,86 @@
1
+ /** The whole namespace. Nothing else in a hash is ours. */
2
+ export declare const DEEP_LINK_PREFIX = "#bitvea-feedback=";
3
+ /**
4
+ * The feedback id a hash names, or null for every other hash there is -
5
+ * including `#pricing`, `#/route/23` and a bare `#`.
6
+ */
7
+ export declare function parseDeepLink(hash: string): string | null;
8
+ /**
9
+ * The URL that opens one item's thread on the page it was filed on. Used by
10
+ * the inbox sheet for an item on another page of the same site.
11
+ *
12
+ * Any existing fragment is dropped, because a URL has one fragment and this
13
+ * one is ours. That is safe precisely because page identity drops the fragment
14
+ * too - see the note at the top of this file - so the URL this returns and the
15
+ * `pageUrl` the item was stored under reduce to the same identity.
16
+ */
17
+ export declare function buildDeepLink(pageUrl: string, feedbackItemId: string): string;
18
+ /**
19
+ * Read the deep link, once. A pure read: nothing about the document changes.
20
+ *
21
+ * It used to strip the fragment here as well, which was a `replaceState` from
22
+ * inside a render pass - a side effect on the HOST's document, in a phase
23
+ * React may run twice and may throw away. The clear is now the shell's job
24
+ * (`clearDeepLinkHash` from an effect), and this function only answers.
25
+ *
26
+ * Nothing downstream may assume the clear stuck in any case: a render-phase
27
+ * clear was overwritten by Next's App Router a moment later, which is why the
28
+ * pin layer's idea of "this page" comes from `currentPageUrl` rather than from
29
+ * whatever the address bar ended up saying.
30
+ */
31
+ export declare function captureDeepLink(): string | null;
32
+ /**
33
+ * The identity of the page the reviewer is on: its URL with no fragment.
34
+ *
35
+ * This is what "which page is this" means everywhere the toolbar asks - the
36
+ * pin layer's page-scoped query, the inbox sheet's scope, and the metadata
37
+ * attached to a new report - and it is deliberately not `location.href`.
38
+ *
39
+ * Two reasons, and both are about the address bar not being ours to trust. A
40
+ * host framework can put our own fragment back after we have removed it
41
+ * (Next's App Router does, see `clearDeepLinkHash`), and a `pageUrl` carrying
42
+ * `#bitvea-feedback=...` matches no stored row. And a deep link has to replace
43
+ * the host's fragment to carry the id at all, so a landing URL and the URL a
44
+ * report was filed from differ by the fragment as a matter of course - see the
45
+ * note at the top of this file for why identity gives the fragment up rather
46
+ * than the other way round.
47
+ *
48
+ * Deriving it instead of trusting the URL makes that class of bug impossible
49
+ * rather than merely unlikely: it is right on the first render, whatever the
50
+ * host does to the address bar afterwards.
51
+ */
52
+ export declare function currentPageUrl(): string;
53
+ /**
54
+ * `href` reduced to a page identity: origin, path and query, no fragment.
55
+ *
56
+ * Every fragment, not only ours. See the note at the top of this file: a URL
57
+ * has one fragment and a deep link needs it, so an identity that keeps the
58
+ * host's cannot be produced by a link we build. Query strings are kept,
59
+ * because they do distinguish pages on a server-rendered host.
60
+ */
61
+ export declare function pageIdentity(href: string): string;
62
+ /**
63
+ * Strip our fragment, leaving the rest of the URL byte-identical. Re-checks
64
+ * the prefix so this can never be the thing that eats a host's own hash, and
65
+ * uses `replaceState` so it adds no history entry the back button has to walk
66
+ * back through.
67
+ *
68
+ * Idempotent, and called more than once on purpose. A render-phase clear is
69
+ * not the last word on the address bar: Next's App Router restores the URL it
70
+ * considered canonical at load - fragment included - from a commit-phase
71
+ * effect, and because a parent's effects run after its children's there is no
72
+ * effect the toolbar can register that is guaranteed to run later. So the
73
+ * shell schedules one more clear onto the next macrotask, which is after the
74
+ * whole commit; see `EnabledToolbar`. Re-checking the prefix is what makes
75
+ * that safe: by then the host may have navigated, and a second clear must
76
+ * never eat a fragment that is not ours.
77
+ */
78
+ export declare function clearDeepLinkHash(): void;
79
+ /**
80
+ * Forget the capture. Only tests need this - a module-scope one-shot is
81
+ * exactly right in a browser, where there is one page load per module, and
82
+ * exactly wrong in a runner where there are many renders per module. It is
83
+ * deliberately not re-exported from the package barrel.
84
+ */
85
+ export declare function resetDeepLinkCapture(): void;
86
+ //# sourceMappingURL=deepLink.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deepLink.d.ts","sourceRoot":"","sources":["../src/deepLink.ts"],"names":[],"mappings":"AA0BA,2DAA2D;AAC3D,eAAO,MAAM,gBAAgB,sBAAsB,CAAC;AAWpD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAazD;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAI7E;AAiBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAQ/C;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAGvC;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAYxC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}