@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.
- package/README.md +262 -0
- package/dist/FeedbackPanel.d.ts +117 -0
- package/dist/FeedbackPanel.d.ts.map +1 -0
- package/dist/FeedbackPanel.js +622 -0
- package/dist/FeedbackPanel.js.map +1 -0
- package/dist/FeedbackToolbar.d.ts +54 -0
- package/dist/FeedbackToolbar.d.ts.map +1 -0
- package/dist/FeedbackToolbar.js +1149 -0
- package/dist/FeedbackToolbar.js.map +1 -0
- package/dist/InboxSheet.d.ts +80 -0
- package/dist/InboxSheet.d.ts.map +1 -0
- package/dist/InboxSheet.js +580 -0
- package/dist/InboxSheet.js.map +1 -0
- package/dist/PinLayer.d.ts +59 -0
- package/dist/PinLayer.d.ts.map +1 -0
- package/dist/PinLayer.js +1702 -0
- package/dist/PinLayer.js.map +1 -0
- package/dist/RichText.d.ts +6 -0
- package/dist/RichText.d.ts.map +1 -0
- package/dist/RichText.js +135 -0
- package/dist/RichText.js.map +1 -0
- package/dist/browser/entry.d.ts +3 -0
- package/dist/browser/entry.d.ts.map +1 -0
- package/dist/browser/entry.js +13 -0
- package/dist/browser/entry.js.map +1 -0
- package/dist/browser/mount.d.ts +37 -0
- package/dist/browser/mount.d.ts.map +1 -0
- package/dist/browser/mount.js +150 -0
- package/dist/browser/mount.js.map +1 -0
- package/dist/browser/toolbar.js +38 -0
- package/dist/browser/toolbar.js.map +7 -0
- package/dist/capture.d.ts +58 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/capture.js +98 -0
- package/dist/capture.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +196 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/fs.d.ts +16 -0
- package/dist/cli/fs.d.ts.map +1 -0
- package/dist/cli/fs.js +33 -0
- package/dist/cli/fs.js.map +1 -0
- package/dist/cli/init.d.ts +75 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +266 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/register.d.ts +55 -0
- package/dist/cli/register.d.ts.map +1 -0
- package/dist/cli/register.js +84 -0
- package/dist/cli/register.js.map +1 -0
- package/dist/cli/registration.d.ts +37 -0
- package/dist/cli/registration.d.ts.map +1 -0
- package/dist/cli/registration.js +69 -0
- package/dist/cli/registration.js.map +1 -0
- package/dist/cli/strategies.d.ts +98 -0
- package/dist/cli/strategies.d.ts.map +1 -0
- package/dist/cli/strategies.js +198 -0
- package/dist/cli/strategies.js.map +1 -0
- package/dist/client.d.ts +59 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +113 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +92 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +65 -0
- package/dist/config.js.map +1 -0
- package/dist/deepLink.d.ts +86 -0
- package/dist/deepLink.d.ts.map +1 -0
- package/dist/deepLink.js +185 -0
- package/dist/deepLink.js.map +1 -0
- package/dist/fontFace.d.ts +70 -0
- package/dist/fontFace.d.ts.map +1 -0
- package/dist/fontFace.js +88 -0
- package/dist/fontFace.js.map +1 -0
- package/dist/gating.d.ts +32 -0
- package/dist/gating.d.ts.map +1 -0
- package/dist/gating.js +49 -0
- package/dist/gating.js.map +1 -0
- package/dist/icons.d.ts +68 -0
- package/dist/icons.d.ts.map +1 -0
- package/dist/icons.js +97 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown.d.ts +59 -0
- package/dist/markdown.d.ts.map +1 -0
- package/dist/markdown.js +130 -0
- package/dist/markdown.js.map +1 -0
- package/dist/motion.d.ts +178 -0
- package/dist/motion.d.ts.map +1 -0
- package/dist/motion.js +302 -0
- package/dist/motion.js.map +1 -0
- package/dist/preferences.d.ts +71 -0
- package/dist/preferences.d.ts.map +1 -0
- package/dist/preferences.js +195 -0
- package/dist/preferences.js.map +1 -0
- package/dist/rasterise.d.ts +45 -0
- package/dist/rasterise.d.ts.map +1 -0
- package/dist/rasterise.js +490 -0
- package/dist/rasterise.js.map +1 -0
- package/dist/selector.d.ts +19 -0
- package/dist/selector.d.ts.map +1 -0
- package/dist/selector.js +142 -0
- package/dist/selector.js.map +1 -0
- package/dist/session.d.ts +19 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +36 -0
- package/dist/session.js.map +1 -0
- package/dist/shortcut.d.ts +88 -0
- package/dist/shortcut.d.ts.map +1 -0
- package/dist/shortcut.js +149 -0
- package/dist/shortcut.js.map +1 -0
- package/dist/tokens.d.ts +709 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +749 -0
- package/dist/tokens.js.map +1 -0
- package/dist/vendor/types/account.d.ts +26 -0
- package/dist/vendor/types/account.d.ts.map +1 -0
- package/dist/vendor/types/account.js +23 -0
- package/dist/vendor/types/account.js.map +1 -0
- package/dist/vendor/types/apiKey.d.ts +38 -0
- package/dist/vendor/types/apiKey.d.ts.map +1 -0
- package/dist/vendor/types/apiKey.js +36 -0
- package/dist/vendor/types/apiKey.js.map +1 -0
- package/dist/vendor/types/auth.d.ts +10 -0
- package/dist/vendor/types/auth.d.ts.map +1 -0
- package/dist/vendor/types/auth.js +15 -0
- package/dist/vendor/types/auth.js.map +1 -0
- package/dist/vendor/types/entity.d.ts +74 -0
- package/dist/vendor/types/entity.d.ts.map +1 -0
- package/dist/vendor/types/entity.js +111 -0
- package/dist/vendor/types/entity.js.map +1 -0
- package/dist/vendor/types/feedback.d.ts +285 -0
- package/dist/vendor/types/feedback.d.ts.map +1 -0
- package/dist/vendor/types/feedback.js +286 -0
- package/dist/vendor/types/feedback.js.map +1 -0
- package/dist/vendor/types/index.d.ts +14 -0
- package/dist/vendor/types/index.d.ts.map +1 -0
- package/dist/vendor/types/index.js +16 -0
- package/dist/vendor/types/index.js.map +1 -0
- package/dist/vendor/types/invite.d.ts +42 -0
- package/dist/vendor/types/invite.d.ts.map +1 -0
- package/dist/vendor/types/invite.js +30 -0
- package/dist/vendor/types/invite.js.map +1 -0
- package/dist/vendor/types/mcpToken.d.ts +38 -0
- package/dist/vendor/types/mcpToken.d.ts.map +1 -0
- package/dist/vendor/types/mcpToken.js +39 -0
- package/dist/vendor/types/mcpToken.js.map +1 -0
- package/dist/vendor/types/organization.d.ts +13 -0
- package/dist/vendor/types/organization.d.ts.map +1 -0
- package/dist/vendor/types/organization.js +12 -0
- package/dist/vendor/types/organization.js.map +1 -0
- package/dist/vendor/types/project.d.ts +73 -0
- package/dist/vendor/types/project.d.ts.map +1 -0
- package/dist/vendor/types/project.js +117 -0
- package/dist/vendor/types/project.js.map +1 -0
- package/dist/vendor/types/role.d.ts +12 -0
- package/dist/vendor/types/role.d.ts.map +1 -0
- package/dist/vendor/types/role.js +20 -0
- package/dist/vendor/types/role.js.map +1 -0
- package/dist/vendor/types/roleDefinition.d.ts +61 -0
- package/dist/vendor/types/roleDefinition.d.ts.map +1 -0
- package/dist/vendor/types/roleDefinition.js +45 -0
- package/dist/vendor/types/roleDefinition.js.map +1 -0
- package/dist/vendor/types/team.d.ts +25 -0
- package/dist/vendor/types/team.d.ts.map +1 -0
- package/dist/vendor/types/team.js +17 -0
- package/dist/vendor/types/team.js.map +1 -0
- package/dist/vendor/types/util.d.ts +39 -0
- package/dist/vendor/types/util.d.ts.map +1 -0
- package/dist/vendor/types/util.js +31 -0
- package/dist/vendor/types/util.js.map +1 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# @bitvea/feedback-toolbar
|
|
2
|
+
|
|
3
|
+
The toolbar a BitVea app installs to collect review feedback from its preview
|
|
4
|
+
deployments. A reviewer opens it on the page they are looking at, pins a
|
|
5
|
+
comment to an element or leaves general feedback, and the submission carries
|
|
6
|
+
the page, viewport, user agent, branch, commit, environment and a screenshot
|
|
7
|
+
with it.
|
|
8
|
+
|
|
9
|
+
## Two ways to install
|
|
10
|
+
|
|
11
|
+
| Route | For | What it costs the host app |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| npm package | Next.js App Router apps, or anything with a bundler | a dependency, an env var, one element in the root layout |
|
|
14
|
+
| `<script src>` | anything with no build step | one tag |
|
|
15
|
+
|
|
16
|
+
Both render the same toolbar, both are gated the same way, and neither ever puts a deploy key in browser-reachable code.
|
|
17
|
+
|
|
18
|
+
### 1. The npm package
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx @bitvea/feedback-toolbar init --api-url https://feedback.bitvea.com
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
That adds the dependency, injects `<FeedbackToolbar apiUrl="https://feedback.bitvea.com" />` into the app's root layout, adds a `prebuild` script carrying the same URL so the deployment registers itself as a project on every build, and writes `NEXT_PUBLIC_FEEDBACK_TOOLBAR_API_URL` into `.env.local` for local development.
|
|
25
|
+
|
|
26
|
+
One thing is left for you, and it is the only one:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
FEEDBACK_TOOLBAR_API_KEY=<a deploy key>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Set it in your hosting provider's environment variables (dashboard > Settings > Deploy keys).
|
|
33
|
+
It is a secret, so it is the one value the installer will not write to disk; without it the `prebuild` step reports that it skipped registration and exits 0, and no project appears.
|
|
34
|
+
|
|
35
|
+
**Why the backend URL is committed rather than left in `.env.local`.**
|
|
36
|
+
It is a `NEXT_PUBLIC_` URL, not a credential, and `.env.local` is gitignored by create-next-app - so a value that lives only there never reaches a build.
|
|
37
|
+
That cost this product two rounds of the same bug: the `prebuild` step reached the build with no URL and skipped itself, and the deployed toolbar had no URL either and rendered nothing, both silently.
|
|
38
|
+
The URL therefore goes where a commit carries it: into the injected element and into the `prebuild` command in `package.json`.
|
|
39
|
+
A prop beats the environment, so deleting the prop is how you go back to configuring the URL per environment.
|
|
40
|
+
|
|
41
|
+
Re-running is a detected no-op: every step recognises its own prior work, so the second run reports "already installed" and writes nothing.
|
|
42
|
+
That includes the build script, which is never added twice - not even to a line somebody has since edited by hand.
|
|
43
|
+
|
|
44
|
+
If it cannot find a root layout to inject into it stops, changes nothing at all, and tells you to place the provider by hand.
|
|
45
|
+
It never leaves a half-install: everything that can fail is worked out before the first write.
|
|
46
|
+
|
|
47
|
+
To place it by hand instead, render it once in `app/layout.tsx`:
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { FeedbackToolbar } from "@bitvea/feedback-toolbar";
|
|
51
|
+
|
|
52
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
53
|
+
return (
|
|
54
|
+
<html lang="en">
|
|
55
|
+
<body>
|
|
56
|
+
{children}
|
|
57
|
+
<FeedbackToolbar />
|
|
58
|
+
</body>
|
|
59
|
+
</html>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
#### Other frameworks
|
|
65
|
+
|
|
66
|
+
The injector is built around pluggable strategies (see `src/cli/strategies.ts`).
|
|
67
|
+
Today there are two: the Next.js App Router, and the script tag below.
|
|
68
|
+
Supporting another framework or another mount point is a new strategy implementing the same interface, not a rewrite of the installer.
|
|
69
|
+
|
|
70
|
+
### 2. The script tag
|
|
71
|
+
|
|
72
|
+
For an app that is not App Router, or that would rather not carry a dependency:
|
|
73
|
+
|
|
74
|
+
```html
|
|
75
|
+
<script src="https://feedback.bitvea.com/toolbar.js"
|
|
76
|
+
data-api-url="https://feedback.bitvea.com"
|
|
77
|
+
data-project-url="https://myapp.com"
|
|
78
|
+
data-environment="preview"
|
|
79
|
+
defer></script>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
One file, its own React bundled in, about 70KB gzipped.
|
|
83
|
+
It reads its whole configuration from the tag, creates the element it mounts into, and mounts the same `<FeedbackToolbar />` the npm package exports.
|
|
84
|
+
There is no second toolbar implementation here, only a second way of starting the one there is.
|
|
85
|
+
|
|
86
|
+
| Attribute | Required | What it does |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| `data-api-url` | yes | Base URL of the BitVea Feedback backend. Without it the toolbar stays dark. |
|
|
89
|
+
| `data-project-url` | yes | The project this page reports against. A static page has no environment to read it from, so it has to be written down. |
|
|
90
|
+
| `data-environment` | to appear at all | The equivalent of `NEXT_PUBLIC_VERCEL_ENV`. `preview` and `development` are on, `production` is off, absent is off. `init` writes `preview`, because a tag it left inert would not be an install; `--environment production` turns it off without removing the tag. |
|
|
91
|
+
| `data-force` | no | `1` forces the toolbar on where the gate would leave it off. For a page that is only ever local. |
|
|
92
|
+
| `data-git-branch`, `data-git-commit` | no | Reported with every submission, when the page knows them. |
|
|
93
|
+
|
|
94
|
+
Two things about this route are worth stating plainly.
|
|
95
|
+
|
|
96
|
+
**The gate is not weakened by it.**
|
|
97
|
+
A closed gate means no markup and no request: the decision is taken before the container element is created, so a disabled install adds nothing at all to the page.
|
|
98
|
+
It is the same `resolveConfig` the npm route uses, fed from the attributes above.
|
|
99
|
+
|
|
100
|
+
**There is no way to hand it a deploy key, on purpose.**
|
|
101
|
+
`data-api-key`, and anything else key-shaped, makes the bundle refuse to mount and log why.
|
|
102
|
+
A key in a page is a key in every visitor's hands, and its whole power is creating projects.
|
|
103
|
+
Register a script-tag install from a terminal or CI instead:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npx @bitvea/feedback-toolbar register --project-url https://myapp.com --api-key <key>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
#### Where the file comes from
|
|
110
|
+
|
|
111
|
+
`build` writes it to `dist/browser/toolbar.js`, with its source map beside it.
|
|
112
|
+
|
|
113
|
+
This repo's own backend serves both: `apps/web/app/toolbar.js/route.ts` reads the built artefact through the package's `./browser-bundle` export and answers `/toolbar.js`, and the map route beside it answers `/toolbar.js.map`.
|
|
114
|
+
A route rather than a file in `public/` because a copy needs a step before `next dev` as well as before `next build`, and the e2e harness starts Next directly; `next.config.ts` names the artefact in `outputFileTracingIncludes`, since a runtime `require.resolve` is invisible to the file tracer.
|
|
115
|
+
`apps/web/e2e/toolbar-script.spec.ts` fetches the served URL and mounts the bytes on a plain page, so the serving half is verified rather than assumed.
|
|
116
|
+
|
|
117
|
+
A different backend can serve the same file straight out of its own public directory instead:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
node ../../packages/toolbar/scripts/build-script-bundle.mjs --out-file ./public/toolbar.js
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The path is `/toolbar.js` on the API's own origin, which is what `init` writes into a page and what `SCRIPT_BUNDLE_PATH` in `src/cli/strategies.ts` says.
|
|
124
|
+
Changing it means changing both.
|
|
125
|
+
|
|
126
|
+
## Registering the project
|
|
127
|
+
|
|
128
|
+
`register` makes sure a deployment exists as a project in the dashboard.
|
|
129
|
+
It is a command in its own right, because two install paths need it: a bundled app runs it from its build, and a script-tag install runs it once by hand.
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
feedback-toolbar register [--project-url <url>] [--api-key <key>] [--api-url <url>]
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Every flag falls back to the build environment:
|
|
136
|
+
|
|
137
|
+
| Flag | Falls back to |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `--api-url` | `NEXT_PUBLIC_FEEDBACK_TOOLBAR_API_URL`, then `FEEDBACK_TOOLBAR_API_URL` |
|
|
140
|
+
| `--project-url` | `VERCEL_PROJECT_PRODUCTION_URL`, then `VERCEL_URL` |
|
|
141
|
+
| `--api-key` | `FEEDBACK_TOOLBAR_API_KEY` |
|
|
142
|
+
|
|
143
|
+
It is idempotent: registering an already-registered URL reports that and changes nothing, which is what makes it safe as a build step that runs on every deploy forever.
|
|
144
|
+
It never guesses a URL, because a registration under a URL nobody meant creates a project that collects feedback from a page that does not exist - it says which piece is missing and stops.
|
|
145
|
+
|
|
146
|
+
`init` wires it in as `prebuild`, and that wiring is why a real install registers anything at all: `init` runs on a laptop, where `VERCEL_URL` does not exist, and the build is where it does.
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{ "scripts": { "prebuild": "feedback-toolbar register --optional" } }
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`--optional` reports a failure and exits 0.
|
|
153
|
+
A feedback tool must not be able to fail somebody's deploy because our backend was slow or a key had been rotated.
|
|
154
|
+
|
|
155
|
+
### The deploy key
|
|
156
|
+
|
|
157
|
+
Read from `--api-key` or `FEEDBACK_TOOLBAR_API_KEY`, and from nowhere else.
|
|
158
|
+
It is never written to disk, never committed, never put in a page, and never reaches a browser.
|
|
159
|
+
The only thing the installer writes into the host app is `NEXT_PUBLIC_FEEDBACK_TOOLBAR_API_URL`, which is a URL.
|
|
160
|
+
|
|
161
|
+
Create one in the dashboard under Settings > Deploy keys.
|
|
162
|
+
A missing key is not an install failure: everything except registration still happens, and the CLI says what to set.
|
|
163
|
+
|
|
164
|
+
## Publishing
|
|
165
|
+
|
|
166
|
+
This is the one package in the repo that is not `"private": true`.
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
pnpm --filter @bitvea/feedback-toolbar release patch # or minor, major, 1.2.3
|
|
170
|
+
pnpm --filter @bitvea/feedback-toolbar release patch --dry-run
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
That sets the version, updates the range `init` writes into host apps, builds both artefacts, proves the built `dist/` stands on its own, and publishes.
|
|
174
|
+
The range matters: `TOOLBAR_VERSION` in `src/cli/init.ts` is a literal, and publishing 0.2.0 while it still says `^0.1.0` installs the old package into every app installed afterwards.
|
|
175
|
+
|
|
176
|
+
Use `pnpm publish`, never `npm publish`.
|
|
177
|
+
The `publishConfig` block that repoints `main`/`types`/`exports` from `src/` at `dist/` is a pnpm feature, and npm ignores it - an npm-published tarball would advertise `./src/index.ts`, which `files` does not even ship.
|
|
178
|
+
`scripts/check-publishable.mjs` asserts that by packing the real tarball and running its binary with no `node_modules` in sight, and it runs on `prepublishOnly`, so publishing cannot skip it.
|
|
179
|
+
|
|
180
|
+
## Who signs in here
|
|
181
|
+
|
|
182
|
+
The toolbar is the **reviewer's** surface, and its sign-in is unchanged and
|
|
183
|
+
deliberately separate from the admin dashboard's.
|
|
184
|
+
|
|
185
|
+
A reviewer asks the toolbar for a magic link, at any address including an
|
|
186
|
+
external one, follows it out of their inbox, and files feedback. That flow goes
|
|
187
|
+
through `POST /api/v1/toolbar-auth/magic-link` on the BitVea Feedback backend.
|
|
188
|
+
|
|
189
|
+
BitVea staff sign in to the dashboard with a `@bitvea.com` Google account
|
|
190
|
+
instead, and a `@bitvea.com` address is refused a magic link here. A reviewer
|
|
191
|
+
never needs the dashboard, and a magic-link session is not admitted there.
|
|
192
|
+
|
|
193
|
+
## Environment
|
|
194
|
+
|
|
195
|
+
| Variable | Required | What it does |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| `NEXT_PUBLIC_FEEDBACK_TOOLBAR_API_URL` | yes | Base URL of the BitVea Feedback backend. Without it the toolbar stays dark rather than rendering a button that fails on click. |
|
|
198
|
+
| `NEXT_PUBLIC_VERCEL_ENV` | populated by Vercel | Drives the gate below. |
|
|
199
|
+
| `NEXT_PUBLIC_FEEDBACK_TOOLBAR_FORCE` | no | Set to `1` to force the toolbar on where the gate would leave it off. |
|
|
200
|
+
|
|
201
|
+
Vercel also populates `NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF`,
|
|
202
|
+
`NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA` and `NEXT_PUBLIC_VERCEL_URL`, which the
|
|
203
|
+
toolbar reads for the branch, commit and project it reports against. All of
|
|
204
|
+
them can be overridden by prop.
|
|
205
|
+
|
|
206
|
+
## When it runs
|
|
207
|
+
|
|
208
|
+
| `NEXT_PUBLIC_VERCEL_ENV` | Toolbar |
|
|
209
|
+
|---|---|
|
|
210
|
+
| `preview` | on |
|
|
211
|
+
| `development` | on |
|
|
212
|
+
| `production` | **off** |
|
|
213
|
+
| absent | **off** |
|
|
214
|
+
| any of the above with `NEXT_PUBLIC_FEEDBACK_TOOLBAR_FORCE=1` | on |
|
|
215
|
+
|
|
216
|
+
Off means off: the component returns `null` before it renders any markup and
|
|
217
|
+
before it issues any request, so a production bundle ships no toolbar DOM and
|
|
218
|
+
makes no network call. That is covered by a test rather than by convention.
|
|
219
|
+
|
|
220
|
+
An absent environment is treated as off on purpose. Not knowing which
|
|
221
|
+
deployment it is running on is not a good enough reason to put a feedback
|
|
222
|
+
widget in front of somebody's customers; a developer who wants it locally sets
|
|
223
|
+
the override.
|
|
224
|
+
|
|
225
|
+
## What `dist/` contains, and why
|
|
226
|
+
|
|
227
|
+
`build` produces two artefacts from one source tree, which is why it is four steps rather than one.
|
|
228
|
+
|
|
229
|
+
1. `tsc -p tsconfig.build.json` - the npm target: many small ES modules for a host app's own bundler to tree-shake, deliberately not bundled. The tests are excluded here and only here; `type-check` still includes them.
|
|
230
|
+
2. `tsc -p tsconfig.vendor-types.json` - `@bitvea/types`, compiled into `dist/vendor/types/`. That package is `"private": true` and depended on with `workspace:*`, a specifier npm cannot resolve, so shipping that dependency produces a package that installs nowhere. Compiling it in beats hand-copying the constants, which would duplicate a Zod schema in TypeScript (cardinal sin 4). `zod` survives as a real dependency, because zod is a published package.
|
|
231
|
+
3. `scripts/add-emitted-import-extensions.mjs` - adds the `.js` Node's ESM loader needs, and retargets `@bitvea/types` at the copy from step 2.
|
|
232
|
+
4. `scripts/build-script-bundle.mjs` - the script-tag target: `dist/browser/toolbar.js`, one minified file with React in it.
|
|
233
|
+
|
|
234
|
+
The script bundle does **not** import `@bitvea/types`; the build replaces it with the plain-data constants alone, read out of the compiled module rather than restated.
|
|
235
|
+
Importing it whole pulled all of Zod in for two integers - 133KB gzipped instead of 70KB - because schemas are built by function calls at module scope and no bundler may drop those.
|
|
236
|
+
On the npm route that cost is the host bundler's business; on this one there is no host bundler to make it somebody else's problem.
|
|
237
|
+
|
|
238
|
+
## Import specifiers inside this package
|
|
239
|
+
|
|
240
|
+
Relative imports inside `src/` carry **no extension**: `import { PinLayer } from "./PinLayer"`.
|
|
241
|
+
`scripts/add-emitted-import-extensions.mjs` adds the `.js` back after `tsc`, as part of `build`.
|
|
242
|
+
Both halves of that are load-bearing, because this package is consumed two ways at once and the two ways disagree about what a relative specifier may look like.
|
|
243
|
+
|
|
244
|
+
`apps/web` consumes it as **source**.
|
|
245
|
+
`package.json` points `main`/`types`/`exports` at `./src/index.ts` and `apps/web/next.config.ts` lists it in `transpilePackages`, so Next.js compiles these files with Turbopack.
|
|
246
|
+
Turbopack does not implement TypeScript's ".js" -> ".ts" resolution rewrite, so a `"./PinLayer.js"` specifier is an unrecoverable "module not found" for the entire `apps/web` build.
|
|
247
|
+
|
|
248
|
+
Real host apps consume the **published `dist/`**, which is plain ESM executed by Node - `dist/cli/bin.js` is run directly by `npx`.
|
|
249
|
+
Node's ESM loader does no extension guessing, so an extensionless specifier there is a runtime `ERR_MODULE_NOT_FOUND`.
|
|
250
|
+
|
|
251
|
+
Extensionless source is the only spelling that needs no opt-in from anyone: Turbopack, Vitest, and every `tsconfig` in this repo (`moduleResolution: "Bundler"`) resolve it as-is.
|
|
252
|
+
Writing the real `.ts`/`.tsx` extension instead also satisfies the bundlers, but then every consumer that type-checks this source has to enable `allowImportingTsExtensions`, and TypeScript 6 still does not rewrite declaration emit - so that route costs three workarounds where this costs one.
|
|
253
|
+
|
|
254
|
+
So: do not "clean up" these specifiers by adding `.js`, and do not drop the post-build step.
|
|
255
|
+
Adding `.js` breaks the `apps/web` build; dropping the step breaks the published package at runtime.
|
|
256
|
+
If you change either, check `pnpm --filter web build` and `node packages/toolbar/dist/cli/bin.js` together.
|
|
257
|
+
|
|
258
|
+
## Styling
|
|
259
|
+
|
|
260
|
+
Everything is inline style from the tokens in `src/tokens.ts`, copied from the
|
|
261
|
+
repository's `DESIGN.md`. The toolbar mounts inside somebody else's app, so it
|
|
262
|
+
can neither rely on that app's design system nor leak into it.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { ToolbarSessionPhase } from "./session.js";
|
|
2
|
+
import type { ToolbarConfig } from "./config.js";
|
|
3
|
+
import { type ToolbarTheme, type ToolbarThemeMode } from "./tokens.js";
|
|
4
|
+
export interface FeedbackPanelProps {
|
|
5
|
+
config: ToolbarConfig;
|
|
6
|
+
/**
|
|
7
|
+
* The resolved session. Owned by the toolbar rather than fetched here,
|
|
8
|
+
* because the pin layer needs the same answer before anything is opened.
|
|
9
|
+
* See `useToolbarSession`.
|
|
10
|
+
*/
|
|
11
|
+
phase: ToolbarSessionPhase;
|
|
12
|
+
/** The reviewer's resolved theme. Defaults to light for standalone use. */
|
|
13
|
+
theme?: ToolbarTheme;
|
|
14
|
+
/** Re-resolve the session, e.g. after the reviewer follows their link. */
|
|
15
|
+
onReloadSession: () => void;
|
|
16
|
+
/** Close the popover. Called after a successful send. */
|
|
17
|
+
onDone?: () => void;
|
|
18
|
+
}
|
|
19
|
+
/** The note composer: free text plus a screenshot of the current viewport. */
|
|
20
|
+
export declare function FeedbackPanel({ config, phase, theme, onReloadSession, onDone, }: FeedbackPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
21
|
+
export interface AccountSurfaceProps extends FeedbackPanelProps {
|
|
22
|
+
/** The reviewer's own choice, which may be "system". */
|
|
23
|
+
mode: ToolbarThemeMode;
|
|
24
|
+
onModeChange: (mode: ToolbarThemeMode) => void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Identity, sign out, and the theme override. The magic-link flow lives here
|
|
28
|
+
* now rather than in the composer: signing in is an account action, and a
|
|
29
|
+
* reviewer who is signed out sees the same surface whichever button they
|
|
30
|
+
* reached for.
|
|
31
|
+
*/
|
|
32
|
+
export declare function AccountSurface({ config, phase, theme, mode, onModeChange, onReloadSession, }: AccountSurfaceProps): import("react/jsx-runtime").JSX.Element;
|
|
33
|
+
/**
|
|
34
|
+
* Two letters from whatever the account has. Magic-link reviewers have no
|
|
35
|
+
* avatar image at all - there is no provider to fetch one from - so initials
|
|
36
|
+
* are the primary representation here rather than a fallback, and the pin
|
|
37
|
+
* markers use the same function so one person reads the same on both.
|
|
38
|
+
*/
|
|
39
|
+
export declare function initialsFor(name: string | undefined, email: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* What to PRINT for a person, as opposed to what to draw in their avatar.
|
|
42
|
+
*
|
|
43
|
+
* Better Auth's magic-link plugin stores `name: name || ""`, and a magic-link
|
|
44
|
+
* sign-up never asks for one - so every invited reviewer has an empty display
|
|
45
|
+
* name, which is most of the people whose words appear in a thread. Printing
|
|
46
|
+
* `author.name` raw therefore rendered an empty bold span followed by a reply
|
|
47
|
+
* body, and a two-person thread read as one unattributed run of text.
|
|
48
|
+
*
|
|
49
|
+
* The address is what identifies them, which is the same fallback the status
|
|
50
|
+
* attribution line and `initialsFor` already use. One helper so the three
|
|
51
|
+
* cannot drift.
|
|
52
|
+
*/
|
|
53
|
+
export declare function displayNameFor(name: string | undefined, email: string): string;
|
|
54
|
+
/**
|
|
55
|
+
* `YYYY-MM-DD HH:mm`, in the reviewer's own timezone.
|
|
56
|
+
*
|
|
57
|
+
* Not `toLocaleString()`: an ISO-shaped stamp sorts as it reads, is the same
|
|
58
|
+
* width on every row so a column of them lines up, and does not silently
|
|
59
|
+
* become `9/4/2026, 12:00:00 PM` for one reviewer and `04.09.2026, 12:00` for
|
|
60
|
+
* the next when they are looking at the same thread. The timezone is the
|
|
61
|
+
* reviewer's own because the question a stamp answers here is "was this before
|
|
62
|
+
* or after the thing I remember doing".
|
|
63
|
+
*/
|
|
64
|
+
export declare function formatStamp(iso: string): string;
|
|
65
|
+
/**
|
|
66
|
+
* "just now", "4m", "3h", "2d" - and `formatStamp` past a week.
|
|
67
|
+
*
|
|
68
|
+
* BESIDE `formatStamp` rather than instead of it, and the two are answering
|
|
69
|
+
* different questions. A thread is a conversation, and the useful fact about
|
|
70
|
+
* the reply above yours is how long ago it landed; that comment's own argument
|
|
71
|
+
* - "was this before or after the thing I remember doing" - is about a row in
|
|
72
|
+
* a list, and it still holds, which is why every relative stamp this renders
|
|
73
|
+
* carries the absolute one in a `title` (issue #20's message header).
|
|
74
|
+
*
|
|
75
|
+
* The relative units are deliberately ASCII-short and locale-free, for exactly
|
|
76
|
+
* the reason `formatStamp` refuses `toLocaleString`: `Intl.RelativeTimeFormat`
|
|
77
|
+
* would render "4 minutes ago" for one reviewer and "vor 4 Minuten" for the
|
|
78
|
+
* next, at two different widths, in a 12px mono cluster that has to stay one
|
|
79
|
+
* line.
|
|
80
|
+
*
|
|
81
|
+
* `now` is an argument rather than a `Date.now()` read so a test can state the
|
|
82
|
+
* moment instead of racing the clock. There is deliberately no ticking
|
|
83
|
+
* interval behind this: the thread re-reads its item after every reply and
|
|
84
|
+
* every status move, and a `setInterval` per open thread inside somebody
|
|
85
|
+
* else's page is a cost with no reader.
|
|
86
|
+
*/
|
|
87
|
+
export declare function relativeStamp(iso: string, now?: number): string;
|
|
88
|
+
export declare function Avatar({ theme, name, email, size, }: {
|
|
89
|
+
theme: ToolbarTheme;
|
|
90
|
+
name?: string | undefined;
|
|
91
|
+
email: string;
|
|
92
|
+
size?: number;
|
|
93
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
94
|
+
export declare function inputStyle(theme: ToolbarTheme): React.CSSProperties;
|
|
95
|
+
export declare function Stack({ children, gap, }: {
|
|
96
|
+
children: React.ReactNode;
|
|
97
|
+
gap?: string;
|
|
98
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
99
|
+
export declare function Muted({ children, role, theme, }: {
|
|
100
|
+
children: React.ReactNode;
|
|
101
|
+
role?: string;
|
|
102
|
+
theme: ToolbarTheme;
|
|
103
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
104
|
+
export declare function ErrorText({ children, theme, }: {
|
|
105
|
+
children: React.ReactNode;
|
|
106
|
+
theme: ToolbarTheme;
|
|
107
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
108
|
+
export declare function Button({ children, onClick, type, disabled, theme, tone, }: {
|
|
109
|
+
children: React.ReactNode;
|
|
110
|
+
onClick?: () => void;
|
|
111
|
+
type?: "button" | "submit";
|
|
112
|
+
disabled?: boolean;
|
|
113
|
+
theme: ToolbarTheme;
|
|
114
|
+
/** `quiet` is the secondary action next to a primary one. */
|
|
115
|
+
tone?: "solid" | "quiet";
|
|
116
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
117
|
+
//# sourceMappingURL=FeedbackPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FeedbackPanel.d.ts","sourceRoot":"","sources":["../src/FeedbackPanel.tsx"],"names":[],"mappings":"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"}
|