@alliance-droid/status-feedback-system 4.3.1 → 4.3.3
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/CHANGELOG.md +30 -17
- package/README.md +457 -457
- package/dist/api.d.ts +2 -0
- package/dist/components/board/FeedbackDetail.svelte +37 -22
- package/dist/components/board/FeedbackItem.svelte +19 -26
- package/dist/components/board/FeedbackList.svelte +2 -2
- package/dist/components/board/FeedbackSidebar.svelte +11 -11
- package/dist/components/board/StatusBadgeMenu.svelte +32 -32
- package/dist/components/board/screenshot-src.d.ts +16 -0
- package/dist/components/board/screenshot-src.js +24 -0
- package/dist/components/board/state.svelte.d.ts +1 -1
- package/package.json +70 -70
package/README.md
CHANGED
|
@@ -1,457 +1,457 @@
|
|
|
1
|
-
# @alliance-droid/status-feedback-system
|
|
2
|
-
|
|
3
|
-
Drop-in system status pages and user feedback components for SvelteKit apps.
|
|
4
|
-
|
|
5
|
-
This package provides UI components plus an optional SvelteKit server proxy for talking to a [status-feedback-admin](https://alliance.ghe.com/alliance/status-feedback-admin) backend. The admin app handles data storage, API key management, and the admin dashboard.
|
|
6
|
-
|
|
7
|
-
**Upgrading an existing app to 4.3?** Read [Upgrading to 4.3](#upgrading-to-43), especially if your proxy replaces a `server-delegated` placeholder with an access token. This guide is included in the installed package's `README.md`.
|
|
8
|
-
|
|
9
|
-
## Architecture
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
┌─────────────────────────┐ ┌──────────────────────────┐
|
|
13
|
-
│ Your SvelteKit App │ │ status-feedback-admin │
|
|
14
|
-
│ │ │ │
|
|
15
|
-
│ ┌───────────────────┐ │ API │ ┌────────────────────┐ │
|
|
16
|
-
│ │ StatusPage │──┼───────┼──│ /api/status │ │
|
|
17
|
-
│ │ FeedbackButton │ │ │ │ /api/feedback │ │
|
|
18
|
-
│ │ DevFeedback │ │ │ │ /api/incidents │ │
|
|
19
|
-
│ │ Feedback board │ │ │ │ /api/board │ │
|
|
20
|
-
│ │ (composable) │ │ │ │ │ │
|
|
21
|
-
│ └───────────────────┘ │ │ └────────────────────┘ │
|
|
22
|
-
│ │ │ │ │
|
|
23
|
-
│ npm package │ │ MSSQL + Azure Blob │
|
|
24
|
-
└─────────────────────────┘ └──────────────────────────┘
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Setup
|
|
28
|
-
|
|
29
|
-
### 1. Deploy the admin backend
|
|
30
|
-
|
|
31
|
-
The admin app ([status-feedback-admin](https://alliance.ghe.com/alliance/status-feedback-admin)) provides the API, database, and admin dashboard. Deploy it first.
|
|
32
|
-
|
|
33
|
-
Required env vars on the admin app:
|
|
34
|
-
```bash
|
|
35
|
-
MSSQL_CONNECTION_STRING=Server=...;Initial Catalog=StatusFeedback;...
|
|
36
|
-
SESSION_SECRET=your-secret
|
|
37
|
-
|
|
38
|
-
# Optional — stores screenshots in Azure Blob Storage instead of base64 in DB
|
|
39
|
-
AZURE_FEEDBACK_BLOB_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=...
|
|
40
|
-
AZURE_FEEDBACK_BLOB_CONTAINER=screenshots
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### 2. Create a project and keep the API key server-side
|
|
44
|
-
|
|
45
|
-
Log into the admin dashboard, create a project, and copy its API key (prefixed `sf_`). Store it in a private server-side environment variable, not a `PUBLIC_` / browser-delivered variable.
|
|
46
|
-
|
|
47
|
-
### 3. Install the client package
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
npm install @alliance-droid/status-feedback-system
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
**Peer dependencies:** `svelte ^5.55.7`, `@alliance-droid/svelte-component-library >=1.3.2`
|
|
54
|
-
|
|
55
|
-
> `@alliance-droid/svelte-component-library` must be `>=1.3.2` — earlier versions are incompatible with Svelte `5.56+` and throw `target.exclude.has is not a function` when board components render.
|
|
56
|
-
|
|
57
|
-
### 4. Add the recommended SvelteKit proxy
|
|
58
|
-
|
|
59
|
-
Use one catch-all route in the consuming app. The package owns the admin API path mapping, request validation, key injection, and `key` query stripping; the app owns auth and app-specific policy.
|
|
60
|
-
|
|
61
|
-
```ts
|
|
62
|
-
// src/routes/svc/feedback/[...path]/+server.ts
|
|
63
|
-
import { env } from '$env/dynamic/private';
|
|
64
|
-
import { createSvelteKitFeedbackProxy } from '@alliance-droid/status-feedback-system/sveltekit/server';
|
|
65
|
-
|
|
66
|
-
export const { GET, POST, PATCH } = createSvelteKitFeedbackProxy({
|
|
67
|
-
apiUrl: env.SF_API_URL,
|
|
68
|
-
apiKey: env.SF_API_KEY,
|
|
69
|
-
resolveUser: async (event) => {
|
|
70
|
-
const user = event.locals.user;
|
|
71
|
-
return user ? { email: user.email, roles: user.roles } : null;
|
|
72
|
-
},
|
|
73
|
-
requireUser: true,
|
|
74
|
-
canSubmitInAppFeedback: ({ user }) =>
|
|
75
|
-
user.roles.includes('QA') || user.roles.includes('ProductOwner'),
|
|
76
|
-
});
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
Then point components at the same-origin route and omit `apiKey`:
|
|
80
|
-
|
|
81
|
-
```svelte
|
|
82
|
-
<DevFeedback apiUrl="/svc/feedback/dev" enabled={canSubmitFeedback} userEmail={user.email} />
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
Direct admin calls with `apiKey="sf_..."` remain supported for existing integrations, but the proxy is the hardened default.
|
|
86
|
-
|
|
87
|
-
## Upgrading to 4.3
|
|
88
|
-
|
|
89
|
-
### Which consumers need a change?
|
|
90
|
-
|
|
91
|
-
Version 4.3 discovers management permissions on **`GET /api/board`**. Earlier versions probed an item's `/transitions` endpoint after loading the board. The board now needs an authenticated list response containing `canManageStatus` and per-item `canChangeStatus` / `needsInput` fields before showing editable controls or the **Needs my input** filter.
|
|
92
|
-
|
|
93
|
-
| Integration | Required action |
|
|
94
|
-
| --- | --- |
|
|
95
|
-
| `createAuthTokenGetter()` or another real access-token getter with the package's 4.3 proxy | No additional proxy change for token forwarding. The package forwards the token on board reads automatically. Check the backend and access prerequisites below. |
|
|
96
|
-
| Direct admin calls with a real access-token getter | No custom proxy change. The 4.3 client sends the token on board reads; backend and access prerequisites still apply. |
|
|
97
|
-
| A custom proxy that forwards bearer tokens only on `/transitions` and `/status` | Also forward the user's bearer token on `GET /api/board`, including filtered and paginated reads. |
|
|
98
|
-
| A server-delegated proxy, such as SkyBridgeSPCC, with `getAuthToken: () => 'server-delegated'` | Replace the placeholder with the signed-in user's real access token on `GET /api/board` too. Passing the placeholder upstream makes the board read-only. |
|
|
99
|
-
| No management token getter | The board remains read-only for status management. Configure authenticated token delivery to enable manager controls. |
|
|
100
|
-
|
|
101
|
-
**Installing 4.3.1 provides these instructions; it does not update custom routes in consuming apps.** Apps using the affected patterns must update their proxy and deploy that app change.
|
|
102
|
-
|
|
103
|
-
### Fix a custom server-delegated proxy
|
|
104
|
-
|
|
105
|
-
In your app's catch-all feedback route (typically `src/routes/svc/feedback/[...path]/+server.ts`), extend the existing token-delegation wrapper to cover these requests:
|
|
106
|
-
|
|
107
|
-
| Request | Token handling |
|
|
108
|
-
| --- | --- |
|
|
109
|
-
| `GET api/board` | Attach the signed-in user's access token when available. Ordinary reads may continue without one; `status=needs_input` requires management access. |
|
|
110
|
-
| `GET api/board/feedback/:id/transitions` | Keep the existing authenticated transition lookup. |
|
|
111
|
-
| `PATCH api/board/feedback/:id/status` | Keep the existing authenticated mutation and same-origin protections. |
|
|
112
|
-
|
|
113
|
-
For a SkyBridgeSPCC-style wrapper, replace the status-only route predicate with:
|
|
114
|
-
|
|
115
|
-
```ts
|
|
116
|
-
import type { RequestEvent } from '@sveltejs/kit';
|
|
117
|
-
|
|
118
|
-
function statusTokenRequirement(event: RequestEvent): 'optional' | 'required' | null {
|
|
119
|
-
const method = event.request.method.toUpperCase();
|
|
120
|
-
const path = (event.params.path ?? '').replace(/^\/+|\/+$/g, '');
|
|
121
|
-
if (method === 'GET' && path === 'api/board') return 'optional';
|
|
122
|
-
if (
|
|
123
|
-
(method === 'GET' && /^api\/board\/feedback\/[^/]+\/transitions$/.test(path)) ||
|
|
124
|
-
(method === 'PATCH' && /^api\/board\/feedback\/[^/]+\/status$/.test(path))
|
|
125
|
-
) return 'required';
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
At the start of the existing delegation wrapper, use the classification to select the token. The following is a replacement fragment: `isSameOriginStatusRequest`, `forbiddenFeedbackResponse`, `resolveFeedbackUser`, and `mintStatusAccessToken` are your app's existing helpers, not package exports.
|
|
131
|
-
|
|
132
|
-
```ts
|
|
133
|
-
const requirement = statusTokenRequirement(event);
|
|
134
|
-
if (!requirement) return event;
|
|
135
|
-
|
|
136
|
-
const sameOrigin = isSameOriginStatusRequest(event);
|
|
137
|
-
if (!sameOrigin && requirement === 'required') return forbiddenFeedbackResponse();
|
|
138
|
-
if (!resolveFeedbackUser(event)) return event;
|
|
139
|
-
|
|
140
|
-
const accessToken = sameOrigin ? await mintStatusAccessToken(event) : null;
|
|
141
|
-
const headers = new Headers(event.request.headers);
|
|
142
|
-
if (accessToken) {
|
|
143
|
-
headers.set('Authorization', `Bearer ${accessToken}`);
|
|
144
|
-
} else {
|
|
145
|
-
headers.delete('Authorization');
|
|
146
|
-
}
|
|
147
|
-
// Continue with the existing code that clones event.request using these headers
|
|
148
|
-
// and passes the prepared event to the package proxy handler.
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
Ensure your exported **GET handler runs this wrapper for board reads**, as well as transitions. Keep the app's session and authorization checks. Your same-origin check must reject cross-site delegation; ordinary board reads without same-origin evidence can continue without a bearer token. Never fall back to the placeholder or a browser-supplied token when server delegation fails. The delegated token must represent the signed-in user, not an application client-credentials identity.
|
|
152
|
-
|
|
153
|
-
For a custom pass-through proxy, forward the real `Authorization` header instead of minting a token. Both patterns must preserve `status=needs_input`, `category`, `sort`, `limit`, and `offset`, return the capability fields unchanged, and avoid shared caching of authenticated board responses. The package's 4.3 proxy handles these details when your wrapper passes it the prepared event.
|
|
154
|
-
|
|
155
|
-
### Backend and access prerequisites
|
|
156
|
-
|
|
157
|
-
1. Apply the admin server's `20260908120000_response_source` migration before deploying the matching admin update. An older server does not return the new capability fields; upgrading the client alone cannot enable them.
|
|
158
|
-
2. Use Svelte `^5.55.7` and component library `>=1.3.2` in the consuming app.
|
|
159
|
-
3. Request an Alliance **access token** with audience and scope `status-feedback-admin`, a user subject (`sub`), client identity (`client_id`), expiration, and the intended role claims. An ID token or placeholder does not satisfy this contract. If scopes or refresh-token configuration changed, sign out and back in.
|
|
160
|
-
4.
|
|
161
|
-
|
|
162
|
-
### Check missing controls after upgrading
|
|
163
|
-
|
|
164
|
-
Inspect the JSON returned by your app's board request, such as `/svc/feedback/api/board?status=open`:
|
|
165
|
-
|
|
166
|
-
| Result | What to check |
|
|
167
|
-
| --- | --- |
|
|
168
|
-
| `data.canManageStatus` is absent | The app may point at an older server, or its proxy may be dropping the new fields. Check the actual `SF_API_URL` deployment. |
|
|
169
|
-
| `data.canManageStatus` is `false` | Check token delivery, token scope/audience/expiry, the user's role, and feedback
|
|
170
|
-
| `data.canManageStatus` is `true`, but an item's `canChangeStatus` is `false` | That item's current workflow state has no consumer action available. Try an Open item to check the manager controls. |
|
|
171
|
-
| The **Needs my input** request returns 403 | The server did not authorize project management for that request. Check that the same token reaches filtered and paginated board reads. |
|
|
172
|
-
|
|
173
|
-
After updating the proxy, reload the board as an
|
|
174
|
-
|
|
175
|
-
## Components
|
|
176
|
-
|
|
177
|
-
### StatusPage
|
|
178
|
-
|
|
179
|
-
Full status page with systems, incidents, and uptime history bars. Supports **self-fetching** (provide `apiUrl`; add `apiKey` only for direct admin calls) or **pass-through** (provide data directly).
|
|
180
|
-
|
|
181
|
-
```svelte
|
|
182
|
-
<script>
|
|
183
|
-
import { StatusPage } from '@alliance-droid/status-feedback-system';
|
|
184
|
-
</script>
|
|
185
|
-
|
|
186
|
-
<!-- Self-fetching through the same-origin proxy -->
|
|
187
|
-
<StatusPage
|
|
188
|
-
projectName="My App"
|
|
189
|
-
apiUrl="/svc/feedback"
|
|
190
|
-
/>
|
|
191
|
-
|
|
192
|
-
<!-- Pass-through mode -->
|
|
193
|
-
<StatusPage
|
|
194
|
-
projectName="My App"
|
|
195
|
-
systems={data.systems}
|
|
196
|
-
incidents={data.incidents}
|
|
197
|
-
statusHistory={data.statusHistory}
|
|
198
|
-
showUptime={true}
|
|
199
|
-
/>
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
| Prop | Type | Default | Description |
|
|
203
|
-
|------|------|---------|-------------|
|
|
204
|
-
| `projectName` | `string` | required | Display name in the header |
|
|
205
|
-
| `apiUrl` | `string` | — | Proxy URL or direct admin backend URL (self-fetching mode) |
|
|
206
|
-
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
207
|
-
| `systems` | `System[]` | — | Pass-through: system data |
|
|
208
|
-
| `incidents` | `Incident[]` | — | Pass-through: incident data |
|
|
209
|
-
| `statusHistory` | `Record<string, StatusHistoryEntry[]>` | — | Pass-through: uptime data |
|
|
210
|
-
| `showUptime` | `boolean` | `true` | Show 90-day uptime bars |
|
|
211
|
-
|
|
212
|
-
### StatusBanner
|
|
213
|
-
|
|
214
|
-
Compact inline status indicator — good for footers or nav bars.
|
|
215
|
-
|
|
216
|
-
```svelte
|
|
217
|
-
<StatusBanner
|
|
218
|
-
apiUrl="/svc/feedback"
|
|
219
|
-
statusUrl="/status"
|
|
220
|
-
/>
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
| Prop | Type | Default | Description |
|
|
224
|
-
|------|------|---------|-------------|
|
|
225
|
-
| `systems` | `System[]` | — | Pass-through mode |
|
|
226
|
-
| `apiUrl` | `string` | — | Proxy URL or direct admin backend URL |
|
|
227
|
-
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
228
|
-
| `statusUrl` | `string` | — | Links to full status page (renders as `<a>`) |
|
|
229
|
-
|
|
230
|
-
### FeedbackButton
|
|
231
|
-
|
|
232
|
-
Floating action button with an embedded feedback form. Fixed position, bottom corner.
|
|
233
|
-
|
|
234
|
-
```svelte
|
|
235
|
-
<FeedbackButton
|
|
236
|
-
apiUrl="/svc/feedback"
|
|
237
|
-
position="bottom-right"
|
|
238
|
-
categories={['Bug', 'Feature Request', 'General']}
|
|
239
|
-
userEmail={currentUser?.email}
|
|
240
|
-
/>
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
| Prop | Type | Default | Description |
|
|
244
|
-
|------|------|---------|-------------|
|
|
245
|
-
| `apiUrl` | `string` | required | Proxy URL or direct admin backend URL |
|
|
246
|
-
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
247
|
-
| `categories` | `string[]` | `['Bug', 'Feature Request', 'General']` | Category options |
|
|
248
|
-
| `userEmail` | `string` | — | Pre-fill email field |
|
|
249
|
-
| `position` | `'bottom-right' \| 'bottom-left'` | `'bottom-right'` | Button position |
|
|
250
|
-
|
|
251
|
-
### FeedbackForm
|
|
252
|
-
|
|
253
|
-
Standalone feedback form — embed it wherever you need it.
|
|
254
|
-
|
|
255
|
-
```svelte
|
|
256
|
-
<FeedbackForm
|
|
257
|
-
apiUrl="/svc/feedback"
|
|
258
|
-
categories={['Bug', 'Feature Request']}
|
|
259
|
-
userEmail={currentUser?.email}
|
|
260
|
-
onSuccess={() => toast('Thanks!')}
|
|
261
|
-
onError={(msg) => toast(msg, 'error')}
|
|
262
|
-
/>
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
| Prop | Type | Default | Description |
|
|
266
|
-
|------|------|---------|-------------|
|
|
267
|
-
| `apiUrl` | `string` | required | Proxy URL or direct admin backend URL |
|
|
268
|
-
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
269
|
-
| `categories` | `string[]` | `['Bug', 'Feature Request', 'General']` | Category options |
|
|
270
|
-
| `userEmail` | `string` | — | Pre-fill email field |
|
|
271
|
-
| `onSuccess` | `() => void` | — | Called after successful submission |
|
|
272
|
-
| `onError` | `(error: string) => void` | — | Called on submission error |
|
|
273
|
-
|
|
274
|
-
### Feedback board (composable)
|
|
275
|
-
|
|
276
|
-
> 📖 **Full integration guide:** [docs/custom-feedback-integration.md](docs/custom-feedback-integration.md) — composing the parts into your own shell, status management via `getAuthToken`, list restyling, and the not-configured fallback.
|
|
277
|
-
|
|
278
|
-
The board is composed from parts around **one shared state** (`createFeedbackBoard`), so it drops into your own layout/shell — app rails, breadcrumb, a right-hand detail panel, whatever you have.
|
|
279
|
-
|
|
280
|
-
```svelte
|
|
281
|
-
<script lang="ts">
|
|
282
|
-
import {
|
|
283
|
-
createFeedbackBoard,
|
|
284
|
-
createAuthTokenGetter,
|
|
285
|
-
FeedbackSidebar,
|
|
286
|
-
FeedbackList,
|
|
287
|
-
FeedbackDetail,
|
|
288
|
-
FeedbackSubmitForm,
|
|
289
|
-
type BoardItem,
|
|
290
|
-
} from '@alliance-droid/status-feedback-system';
|
|
291
|
-
|
|
292
|
-
let { data } = $props();
|
|
293
|
-
const board = createFeedbackBoard({
|
|
294
|
-
apiUrl: '/svc/feedback',
|
|
295
|
-
userEmail: data.user?.email,
|
|
296
|
-
getAuthToken: createAuthTokenGetter(), // optional — enables status management
|
|
297
|
-
categoryConfig: {
|
|
298
|
-
Bug: { icon: 'fa-solid fa-bug', label: 'Bugs' },
|
|
299
|
-
'Feature Request': { icon: 'fa-solid fa-lightbulb', label: 'Ideas' },
|
|
300
|
-
},
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
let selected = $state<BoardItem | null>(null);
|
|
304
|
-
</script>
|
|
305
|
-
|
|
306
|
-
<YourSidebar><FeedbackSidebar {board} /></YourSidebar>
|
|
307
|
-
<YourMain><FeedbackList {board} onSelect={(i) => (selected = i)} /></YourMain>
|
|
308
|
-
<YourRail>
|
|
309
|
-
{#if selected}<FeedbackDetail {board} item={selected} />{/if}
|
|
310
|
-
</YourRail>
|
|
311
|
-
<!-- Render <FeedbackSubmitForm {board} /> wherever you want submission. -->
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
`createFeedbackBoard(options)` returns a reactive `FeedbackBoardState` (items, categories, filters, voting, `loadTransitions`/`setStatus`, …) shared by every sub-component.
|
|
315
|
-
|
|
316
|
-
| Option | Type | Default | Description |
|
|
317
|
-
|--------|------|---------|-------------|
|
|
318
|
-
| `apiUrl` | `string` | required | Proxy URL or direct admin backend URL |
|
|
319
|
-
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
320
|
-
| `userEmail` | `string` | — | Authenticated user (required to submit feedback/replies) |
|
|
321
|
-
| `categoryConfig` | `Record<string, { label?, icon?, description? }>` | `{}` | Relabel / add icons to sidebar categories |
|
|
322
|
-
| `getAuthToken` | `() => string \| Promise<string \| null>` | — | Bearer token so privileged users can set status (see below) |
|
|
323
|
-
| `pageSize` | `number` | `50` | Items per page |
|
|
324
|
-
|
|
325
|
-
Exported parts: `createFeedbackBoard`, `FeedbackSidebar`, `FeedbackList`, `FeedbackItem`, `FeedbackDetail`, `FeedbackSubmitForm`.
|
|
326
|
-
|
|
327
|
-
Categories are fixed by the system (**Bug**, **Feature Request**) — `categoryConfig` only relabels or adds icons.
|
|
328
|
-
|
|
329
|
-
#### Setting status (ProductOwners and Admins)
|
|
330
|
-
|
|
331
|
-
When you pass `getAuthToken`, **Admins and ProductOwners
|
|
332
|
-
|
|
333
|
-
Editable items have **purple status controls
|
|
334
|
-
|
|
335
|
-
Deploy the matching admin-server capabilities update first. The packaged proxy forwards the optional board bearer token; custom proxies must also forward `Authorization` on board reads and preserve `status=needs_input`. Custom lists can use the backend's per-item `canChangeStatus` and `needsInput` fields with `board.canManageStatus`.
|
|
336
|
-
|
|
337
|
-
- `getAuthToken` must return the user's **Alliance OIDC access token** whose audience covers **`status-feedback-admin`** (an *id token*, whose `aud` is your app's client id, is rejected). It's called on demand; return `null` for anonymous / non-privileged users and the control simply won't appear — the badge stays a plain read-only badge.
|
|
338
|
-
- **Use the built-in `createAuthTokenGetter()`** for this — it fetches a same-origin token endpoint (`/auth/access-token` by default), caches until just before expiry, single-flights, and returns `null` on any failure. With `@alliance-droid/svelte-auth-core`, set `enableAccessTokenEndpoint: true` and request the `status-feedback-admin` scope so that endpoint exists. Create one instance at module level: `export const getAuthToken = createAuthTokenGetter();`
|
|
339
|
-
- The token travels as `Authorization: Bearer …` to the admin backend, which **verifies it against the IdP** and derives the user's roles. Authorization (Admin
|
|
340
|
-
- The available transitions are returned by the backend per item, so the menu always reflects exactly what that user may do.
|
|
341
|
-
|
|
342
|
-
> Security note: use the SvelteKit proxy to keep the project `apiKey` out of browser-delivered code. Direct admin calls with an embedded key are still supported for existing integrations, but same-origin proxy mode is the recommended default.
|
|
343
|
-
|
|
344
|
-
### DevFeedback
|
|
345
|
-
|
|
346
|
-
Feedback overlay for authorized users. Activated via **Ctrl+right-click** (Cmd+right-click on macOS) anywhere on the page.
|
|
347
|
-
|
|
348
|
-
Auto-captures: screenshots (via html2canvas), console errors, viewport size, current route, clicked element selector, and deployment context (Vercel env vars).
|
|
349
|
-
|
|
350
|
-
**Gating:** the widget shows only when `enabled` is `true` **and** `userEmail` is non-empty. `enabled` is the single gate — wire it to your own rule, typically a role/permission check. There is no environment auto-detection. See [docs/role-based-access.md](docs/role-based-access.md) for the recommended role-based setup and the pitfalls.
|
|
351
|
-
|
|
352
|
-
```svelte
|
|
353
|
-
<script>
|
|
354
|
-
import { DevFeedback } from '@alliance-droid/status-feedback-system';
|
|
355
|
-
</script>
|
|
356
|
-
|
|
357
|
-
<!-- In your root +layout.svelte -->
|
|
358
|
-
<DevFeedback
|
|
359
|
-
apiUrl="/svc/feedback/dev"
|
|
360
|
-
boardUrl="/feedback"
|
|
361
|
-
userEmail={user?.email ?? ''}
|
|
362
|
-
enabled={userCanGiveFeedback}
|
|
363
|
-
/>
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
| Prop | Type | Default | Description |
|
|
367
|
-
|------|------|---------|-------------|
|
|
368
|
-
| `apiUrl` | `string` | required | Proxy URL or direct admin backend URL. Use `/svc/feedback/dev` with the package proxy for stricter widget policy. |
|
|
369
|
-
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
370
|
-
| `enabled` | `boolean` | required | The only gate — wire to your role/permission check |
|
|
371
|
-
| `userEmail` | `string` | required | Signed-in user's email. Empty string ⇒ renders nothing (anonymous feedback unsupported) |
|
|
372
|
-
| `boardUrl` | `string` | — | URL for "View Open Issues" link. Hidden if omitted. |
|
|
373
|
-
| `maxConsoleErrors` | `number` | `20` | Max console errors to capture |
|
|
374
|
-
| `showOnboarding` | `boolean` | `true` | Show "Ctrl+right-click" toast on first visit |
|
|
375
|
-
| `renderTokenEndpoint` | `string` | `/svc/internal/issue-render-token` | App endpoint that mints render tokens for screenshot capture |
|
|
376
|
-
|
|
377
|
-
**Screenshot storage:** If the admin backend has `AZURE_FEEDBACK_BLOB_CONNECTION_STRING` configured, screenshots are uploaded to Azure Blob Storage. Otherwise they're stored as base64 in the database.
|
|
378
|
-
|
|
379
|
-
## API Client
|
|
380
|
-
|
|
381
|
-
All components use a shared API client. You can also use it directly:
|
|
382
|
-
|
|
383
|
-
```typescript
|
|
384
|
-
import { createStatusClient } from '@alliance-droid/status-feedback-system';
|
|
385
|
-
|
|
386
|
-
const client = createStatusClient({
|
|
387
|
-
apiUrl: '/svc/feedback',
|
|
388
|
-
fetch: event.fetch, // Optional: pass SvelteKit's fetch for SSR
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
const { systems, overallStatus } = await client.getStatus();
|
|
392
|
-
const { incidents } = await client.getIncidents({ limit: 5, includeResolved: true });
|
|
393
|
-
const { history } = await client.getUptime({ days: 90 });
|
|
394
|
-
const feedback = await client.submitFeedback({ message: 'Great product!' });
|
|
395
|
-
const { items, total, categories } = await client.getBoard({ sort: 'votes', status: 'open' });
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
## Shared Components
|
|
399
|
-
|
|
400
|
-
Lower-level building blocks used by StatusPage — exported for custom layouts:
|
|
401
|
-
|
|
402
|
-
```typescript
|
|
403
|
-
import {
|
|
404
|
-
StatusIndicator, // Status dot + label (operational/degraded/outage/maintenance)
|
|
405
|
-
UptimeBar, // 90-day uptime bar chart
|
|
406
|
-
IncidentTimeline, // Chronological incident updates
|
|
407
|
-
SystemCard, // Single system status card with uptime bar
|
|
408
|
-
} from '@alliance-droid/status-feedback-system';
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
## All Exports
|
|
412
|
-
|
|
413
|
-
```typescript
|
|
414
|
-
// Components
|
|
415
|
-
import {
|
|
416
|
-
StatusPage, StatusBanner, FeedbackForm, FeedbackButton, DevFeedback,
|
|
417
|
-
StatusIndicator, UptimeBar, IncidentTimeline, SystemCard, PasteImageInput,
|
|
418
|
-
} from '@alliance-droid/status-feedback-system';
|
|
419
|
-
|
|
420
|
-
// Feedback board (composable)
|
|
421
|
-
import {
|
|
422
|
-
createFeedbackBoard,
|
|
423
|
-
createAuthTokenGetter,
|
|
424
|
-
FeedbackSidebar, FeedbackList, FeedbackItem, FeedbackDetail, FeedbackSubmitForm,
|
|
425
|
-
} from '@alliance-droid/status-feedback-system';
|
|
426
|
-
import type {
|
|
427
|
-
FeedbackBoardState, FeedbackBoardOptions, CategoryConfig,
|
|
428
|
-
AuthTokenGetter, AuthTokenGetterOptions,
|
|
429
|
-
} from '@alliance-droid/status-feedback-system';
|
|
430
|
-
|
|
431
|
-
// API Client
|
|
432
|
-
import { createStatusClient } from '@alliance-droid/status-feedback-system';
|
|
433
|
-
import type {
|
|
434
|
-
StatusClient, StatusClientConfig,
|
|
435
|
-
StatusData, IncidentData, UptimeData, BoardData, BoardItem, BoardCategory,
|
|
436
|
-
} from '@alliance-droid/status-feedback-system';
|
|
437
|
-
|
|
438
|
-
// SvelteKit server proxy
|
|
439
|
-
import {
|
|
440
|
-
createSvelteKitFeedbackProxy,
|
|
441
|
-
isFeedbackProxyConfigured,
|
|
442
|
-
} from '@alliance-droid/status-feedback-system/sveltekit/server';
|
|
443
|
-
|
|
444
|
-
// Utilities
|
|
445
|
-
import { deriveOverallStatus } from '@alliance-droid/status-feedback-system';
|
|
446
|
-
|
|
447
|
-
// Types
|
|
448
|
-
import type {
|
|
449
|
-
Project, System, Incident, IncidentUpdate, Feedback, FeedbackResponse,
|
|
450
|
-
FeedbackVisibility, StatusHistoryEntry, ApiResponse,
|
|
451
|
-
SystemStatus, IncidentStatus, IncidentSeverity, FeedbackStatus,
|
|
452
|
-
} from '@alliance-droid/status-feedback-system';
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
## License
|
|
456
|
-
|
|
457
|
-
Copyright © 2026 Alliance Technical Group. All rights reserved.
|
|
1
|
+
# @alliance-droid/status-feedback-system
|
|
2
|
+
|
|
3
|
+
Drop-in system status pages and user feedback components for SvelteKit apps.
|
|
4
|
+
|
|
5
|
+
This package provides UI components plus an optional SvelteKit server proxy for talking to a [status-feedback-admin](https://alliance.ghe.com/alliance/status-feedback-admin) backend. The admin app handles data storage, API key management, and the admin dashboard.
|
|
6
|
+
|
|
7
|
+
**Upgrading an existing app to 4.3?** Read [Upgrading to 4.3](#upgrading-to-43), especially if your proxy replaces a `server-delegated` placeholder with an access token. This guide is included in the installed package's `README.md`.
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
┌─────────────────────────┐ ┌──────────────────────────┐
|
|
13
|
+
│ Your SvelteKit App │ │ status-feedback-admin │
|
|
14
|
+
│ │ │ │
|
|
15
|
+
│ ┌───────────────────┐ │ API │ ┌────────────────────┐ │
|
|
16
|
+
│ │ StatusPage │──┼───────┼──│ /api/status │ │
|
|
17
|
+
│ │ FeedbackButton │ │ │ │ /api/feedback │ │
|
|
18
|
+
│ │ DevFeedback │ │ │ │ /api/incidents │ │
|
|
19
|
+
│ │ Feedback board │ │ │ │ /api/board │ │
|
|
20
|
+
│ │ (composable) │ │ │ │ │ │
|
|
21
|
+
│ └───────────────────┘ │ │ └────────────────────┘ │
|
|
22
|
+
│ │ │ │ │
|
|
23
|
+
│ npm package │ │ MSSQL + Azure Blob │
|
|
24
|
+
└─────────────────────────┘ └──────────────────────────┘
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Setup
|
|
28
|
+
|
|
29
|
+
### 1. Deploy the admin backend
|
|
30
|
+
|
|
31
|
+
The admin app ([status-feedback-admin](https://alliance.ghe.com/alliance/status-feedback-admin)) provides the API, database, and admin dashboard. Deploy it first.
|
|
32
|
+
|
|
33
|
+
Required env vars on the admin app:
|
|
34
|
+
```bash
|
|
35
|
+
MSSQL_CONNECTION_STRING=Server=...;Initial Catalog=StatusFeedback;...
|
|
36
|
+
SESSION_SECRET=your-secret
|
|
37
|
+
|
|
38
|
+
# Optional — stores screenshots in Azure Blob Storage instead of base64 in DB
|
|
39
|
+
AZURE_FEEDBACK_BLOB_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=...
|
|
40
|
+
AZURE_FEEDBACK_BLOB_CONTAINER=screenshots
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2. Create a project and keep the API key server-side
|
|
44
|
+
|
|
45
|
+
Log into the admin dashboard, create a project, and copy its API key (prefixed `sf_`). Store it in a private server-side environment variable, not a `PUBLIC_` / browser-delivered variable.
|
|
46
|
+
|
|
47
|
+
### 3. Install the client package
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install @alliance-droid/status-feedback-system
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Peer dependencies:** `svelte ^5.55.7`, `@alliance-droid/svelte-component-library >=1.3.2`
|
|
54
|
+
|
|
55
|
+
> `@alliance-droid/svelte-component-library` must be `>=1.3.2` — earlier versions are incompatible with Svelte `5.56+` and throw `target.exclude.has is not a function` when board components render.
|
|
56
|
+
|
|
57
|
+
### 4. Add the recommended SvelteKit proxy
|
|
58
|
+
|
|
59
|
+
Use one catch-all route in the consuming app. The package owns the admin API path mapping, request validation, key injection, and `key` query stripping; the app owns auth and app-specific policy.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
// src/routes/svc/feedback/[...path]/+server.ts
|
|
63
|
+
import { env } from '$env/dynamic/private';
|
|
64
|
+
import { createSvelteKitFeedbackProxy } from '@alliance-droid/status-feedback-system/sveltekit/server';
|
|
65
|
+
|
|
66
|
+
export const { GET, POST, PATCH } = createSvelteKitFeedbackProxy({
|
|
67
|
+
apiUrl: env.SF_API_URL,
|
|
68
|
+
apiKey: env.SF_API_KEY,
|
|
69
|
+
resolveUser: async (event) => {
|
|
70
|
+
const user = event.locals.user;
|
|
71
|
+
return user ? { email: user.email, roles: user.roles } : null;
|
|
72
|
+
},
|
|
73
|
+
requireUser: true,
|
|
74
|
+
canSubmitInAppFeedback: ({ user }) =>
|
|
75
|
+
user.roles.includes('QA') || user.roles.includes('ProductOwner'),
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Then point components at the same-origin route and omit `apiKey`:
|
|
80
|
+
|
|
81
|
+
```svelte
|
|
82
|
+
<DevFeedback apiUrl="/svc/feedback/dev" enabled={canSubmitFeedback} userEmail={user.email} />
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Direct admin calls with `apiKey="sf_..."` remain supported for existing integrations, but the proxy is the hardened default.
|
|
86
|
+
|
|
87
|
+
## Upgrading to 4.3
|
|
88
|
+
|
|
89
|
+
### Which consumers need a change?
|
|
90
|
+
|
|
91
|
+
Version 4.3 discovers management permissions on **`GET /api/board`**. Earlier versions probed an item's `/transitions` endpoint after loading the board. The board now needs an authenticated list response containing `canManageStatus` and per-item `canChangeStatus` / `needsInput` fields before showing editable controls or the **Needs my input** filter.
|
|
92
|
+
|
|
93
|
+
| Integration | Required action |
|
|
94
|
+
| --- | --- |
|
|
95
|
+
| `createAuthTokenGetter()` or another real access-token getter with the package's 4.3 proxy | No additional proxy change for token forwarding. The package forwards the token on board reads automatically. Check the backend and access prerequisites below. |
|
|
96
|
+
| Direct admin calls with a real access-token getter | No custom proxy change. The 4.3 client sends the token on board reads; backend and access prerequisites still apply. |
|
|
97
|
+
| A custom proxy that forwards bearer tokens only on `/transitions` and `/status` | Also forward the user's bearer token on `GET /api/board`, including filtered and paginated reads. |
|
|
98
|
+
| A server-delegated proxy, such as SkyBridgeSPCC, with `getAuthToken: () => 'server-delegated'` | Replace the placeholder with the signed-in user's real access token on `GET /api/board` too. Passing the placeholder upstream makes the board read-only. |
|
|
99
|
+
| No management token getter | The board remains read-only for status management. Configure authenticated token delivery to enable manager controls. |
|
|
100
|
+
|
|
101
|
+
**Installing 4.3.1 provides these instructions; it does not update custom routes in consuming apps.** Apps using the affected patterns must update their proxy and deploy that app change.
|
|
102
|
+
|
|
103
|
+
### Fix a custom server-delegated proxy
|
|
104
|
+
|
|
105
|
+
In your app's catch-all feedback route (typically `src/routes/svc/feedback/[...path]/+server.ts`), extend the existing token-delegation wrapper to cover these requests:
|
|
106
|
+
|
|
107
|
+
| Request | Token handling |
|
|
108
|
+
| --- | --- |
|
|
109
|
+
| `GET api/board` | Attach the signed-in user's access token when available. Ordinary reads may continue without one; `status=needs_input` requires management access. |
|
|
110
|
+
| `GET api/board/feedback/:id/transitions` | Keep the existing authenticated transition lookup. |
|
|
111
|
+
| `PATCH api/board/feedback/:id/status` | Keep the existing authenticated mutation and same-origin protections. |
|
|
112
|
+
|
|
113
|
+
For a SkyBridgeSPCC-style wrapper, replace the status-only route predicate with:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import type { RequestEvent } from '@sveltejs/kit';
|
|
117
|
+
|
|
118
|
+
function statusTokenRequirement(event: RequestEvent): 'optional' | 'required' | null {
|
|
119
|
+
const method = event.request.method.toUpperCase();
|
|
120
|
+
const path = (event.params.path ?? '').replace(/^\/+|\/+$/g, '');
|
|
121
|
+
if (method === 'GET' && path === 'api/board') return 'optional';
|
|
122
|
+
if (
|
|
123
|
+
(method === 'GET' && /^api\/board\/feedback\/[^/]+\/transitions$/.test(path)) ||
|
|
124
|
+
(method === 'PATCH' && /^api\/board\/feedback\/[^/]+\/status$/.test(path))
|
|
125
|
+
) return 'required';
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
At the start of the existing delegation wrapper, use the classification to select the token. The following is a replacement fragment: `isSameOriginStatusRequest`, `forbiddenFeedbackResponse`, `resolveFeedbackUser`, and `mintStatusAccessToken` are your app's existing helpers, not package exports.
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
const requirement = statusTokenRequirement(event);
|
|
134
|
+
if (!requirement) return event;
|
|
135
|
+
|
|
136
|
+
const sameOrigin = isSameOriginStatusRequest(event);
|
|
137
|
+
if (!sameOrigin && requirement === 'required') return forbiddenFeedbackResponse();
|
|
138
|
+
if (!resolveFeedbackUser(event)) return event;
|
|
139
|
+
|
|
140
|
+
const accessToken = sameOrigin ? await mintStatusAccessToken(event) : null;
|
|
141
|
+
const headers = new Headers(event.request.headers);
|
|
142
|
+
if (accessToken) {
|
|
143
|
+
headers.set('Authorization', `Bearer ${accessToken}`);
|
|
144
|
+
} else {
|
|
145
|
+
headers.delete('Authorization');
|
|
146
|
+
}
|
|
147
|
+
// Continue with the existing code that clones event.request using these headers
|
|
148
|
+
// and passes the prepared event to the package proxy handler.
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Ensure your exported **GET handler runs this wrapper for board reads**, as well as transitions. Keep the app's session and authorization checks. Your same-origin check must reject cross-site delegation; ordinary board reads without same-origin evidence can continue without a bearer token. Never fall back to the placeholder or a browser-supplied token when server delegation fails. The delegated token must represent the signed-in user, not an application client-credentials identity.
|
|
152
|
+
|
|
153
|
+
For a custom pass-through proxy, forward the real `Authorization` header instead of minting a token. Both patterns must preserve `status=needs_input`, `category`, `sort`, `limit`, and `offset`, return the capability fields unchanged, and avoid shared caching of authenticated board responses. The package's 4.3 proxy handles these details when your wrapper passes it the prepared event.
|
|
154
|
+
|
|
155
|
+
### Backend and access prerequisites
|
|
156
|
+
|
|
157
|
+
1. Apply the admin server's `20260908120000_response_source` migration before deploying the matching admin update. An older server does not return the new capability fields; upgrading the client alone cannot enable them.
|
|
158
|
+
2. Use Svelte `^5.55.7` and component library `>=1.3.2` in the consuming app.
|
|
159
|
+
3. Request an Alliance **access token** with audience and scope `status-feedback-admin`, a user subject (`sub`), client identity (`client_id`), expiration, and the intended role claims. An ID token or placeholder does not satisfy this contract. If scopes or refresh-token configuration changed, sign out and back in.
|
|
160
|
+
4. Deploy the admin server's consumer app authorization update and configure `FEEDBACK_PROJECT_CLIENTS` **on the feedback server** once per app: `{"feedback-project-id":["consumer-oauth-client-id"]}`. Use the project ID from its dashboard URL and the exact OAuth `client_id` issued for the consumer app. App **Admin**, **Administrator**, and **ProductOwner** roles (case-insensitive) then grant the restricted board decisions. **Users never need to log into the feedback admin app, be provisioned there, or receive project assignments.** The feedback dashboard remains a separate operator login. This authorization update adds no migration and requires no Identity Server code change or consumer package update if tokens already reach the backend.
|
|
161
|
+
|
|
162
|
+
### Check missing controls after upgrading
|
|
163
|
+
|
|
164
|
+
Inspect the JSON returned by your app's board request, such as `/svc/feedback/api/board?status=open`:
|
|
165
|
+
|
|
166
|
+
| Result | What to check |
|
|
167
|
+
| --- | --- |
|
|
168
|
+
| `data.canManageStatus` is absent | The app may point at an older server, or its proxy may be dropping the new fields. Check the actual `SF_API_URL` deployment. |
|
|
169
|
+
| `data.canManageStatus` is `false` | Check token delivery, token scope/audience/expiry, the user's app role, and that the feedback server's `FEEDBACK_PROJECT_CLIENTS` binds this project to the token's exact `client_id`. Ordinary reads intentionally succeed even when optional authentication fails. |
|
|
170
|
+
| `data.canManageStatus` is `true`, but an item's `canChangeStatus` is `false` | That item's current workflow state has no consumer action available. Try an Open item to check the manager controls. |
|
|
171
|
+
| The **Needs my input** request returns 403 | The server did not authorize project management for that request. Check that the same token reaches filtered and paginated board reads. |
|
|
172
|
+
|
|
173
|
+
After updating the proxy and configuring the feedback server, reload the board as an app Admin or ProductOwner who has never logged into the feedback dashboard. An actionable Open item should show the purple status control; **Needs my input** should be available. Also check an ordinary viewer still gets a read-only status badge.
|
|
174
|
+
|
|
175
|
+
## Components
|
|
176
|
+
|
|
177
|
+
### StatusPage
|
|
178
|
+
|
|
179
|
+
Full status page with systems, incidents, and uptime history bars. Supports **self-fetching** (provide `apiUrl`; add `apiKey` only for direct admin calls) or **pass-through** (provide data directly).
|
|
180
|
+
|
|
181
|
+
```svelte
|
|
182
|
+
<script>
|
|
183
|
+
import { StatusPage } from '@alliance-droid/status-feedback-system';
|
|
184
|
+
</script>
|
|
185
|
+
|
|
186
|
+
<!-- Self-fetching through the same-origin proxy -->
|
|
187
|
+
<StatusPage
|
|
188
|
+
projectName="My App"
|
|
189
|
+
apiUrl="/svc/feedback"
|
|
190
|
+
/>
|
|
191
|
+
|
|
192
|
+
<!-- Pass-through mode -->
|
|
193
|
+
<StatusPage
|
|
194
|
+
projectName="My App"
|
|
195
|
+
systems={data.systems}
|
|
196
|
+
incidents={data.incidents}
|
|
197
|
+
statusHistory={data.statusHistory}
|
|
198
|
+
showUptime={true}
|
|
199
|
+
/>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
| Prop | Type | Default | Description |
|
|
203
|
+
|------|------|---------|-------------|
|
|
204
|
+
| `projectName` | `string` | required | Display name in the header |
|
|
205
|
+
| `apiUrl` | `string` | — | Proxy URL or direct admin backend URL (self-fetching mode) |
|
|
206
|
+
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
207
|
+
| `systems` | `System[]` | — | Pass-through: system data |
|
|
208
|
+
| `incidents` | `Incident[]` | — | Pass-through: incident data |
|
|
209
|
+
| `statusHistory` | `Record<string, StatusHistoryEntry[]>` | — | Pass-through: uptime data |
|
|
210
|
+
| `showUptime` | `boolean` | `true` | Show 90-day uptime bars |
|
|
211
|
+
|
|
212
|
+
### StatusBanner
|
|
213
|
+
|
|
214
|
+
Compact inline status indicator — good for footers or nav bars.
|
|
215
|
+
|
|
216
|
+
```svelte
|
|
217
|
+
<StatusBanner
|
|
218
|
+
apiUrl="/svc/feedback"
|
|
219
|
+
statusUrl="/status"
|
|
220
|
+
/>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
| Prop | Type | Default | Description |
|
|
224
|
+
|------|------|---------|-------------|
|
|
225
|
+
| `systems` | `System[]` | — | Pass-through mode |
|
|
226
|
+
| `apiUrl` | `string` | — | Proxy URL or direct admin backend URL |
|
|
227
|
+
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
228
|
+
| `statusUrl` | `string` | — | Links to full status page (renders as `<a>`) |
|
|
229
|
+
|
|
230
|
+
### FeedbackButton
|
|
231
|
+
|
|
232
|
+
Floating action button with an embedded feedback form. Fixed position, bottom corner.
|
|
233
|
+
|
|
234
|
+
```svelte
|
|
235
|
+
<FeedbackButton
|
|
236
|
+
apiUrl="/svc/feedback"
|
|
237
|
+
position="bottom-right"
|
|
238
|
+
categories={['Bug', 'Feature Request', 'General']}
|
|
239
|
+
userEmail={currentUser?.email}
|
|
240
|
+
/>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
| Prop | Type | Default | Description |
|
|
244
|
+
|------|------|---------|-------------|
|
|
245
|
+
| `apiUrl` | `string` | required | Proxy URL or direct admin backend URL |
|
|
246
|
+
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
247
|
+
| `categories` | `string[]` | `['Bug', 'Feature Request', 'General']` | Category options |
|
|
248
|
+
| `userEmail` | `string` | — | Pre-fill email field |
|
|
249
|
+
| `position` | `'bottom-right' \| 'bottom-left'` | `'bottom-right'` | Button position |
|
|
250
|
+
|
|
251
|
+
### FeedbackForm
|
|
252
|
+
|
|
253
|
+
Standalone feedback form — embed it wherever you need it.
|
|
254
|
+
|
|
255
|
+
```svelte
|
|
256
|
+
<FeedbackForm
|
|
257
|
+
apiUrl="/svc/feedback"
|
|
258
|
+
categories={['Bug', 'Feature Request']}
|
|
259
|
+
userEmail={currentUser?.email}
|
|
260
|
+
onSuccess={() => toast('Thanks!')}
|
|
261
|
+
onError={(msg) => toast(msg, 'error')}
|
|
262
|
+
/>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
| Prop | Type | Default | Description |
|
|
266
|
+
|------|------|---------|-------------|
|
|
267
|
+
| `apiUrl` | `string` | required | Proxy URL or direct admin backend URL |
|
|
268
|
+
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
269
|
+
| `categories` | `string[]` | `['Bug', 'Feature Request', 'General']` | Category options |
|
|
270
|
+
| `userEmail` | `string` | — | Pre-fill email field |
|
|
271
|
+
| `onSuccess` | `() => void` | — | Called after successful submission |
|
|
272
|
+
| `onError` | `(error: string) => void` | — | Called on submission error |
|
|
273
|
+
|
|
274
|
+
### Feedback board (composable)
|
|
275
|
+
|
|
276
|
+
> 📖 **Full integration guide:** [docs/custom-feedback-integration.md](docs/custom-feedback-integration.md) — composing the parts into your own shell, status management via `getAuthToken`, list restyling, and the not-configured fallback.
|
|
277
|
+
|
|
278
|
+
The board is composed from parts around **one shared state** (`createFeedbackBoard`), so it drops into your own layout/shell — app rails, breadcrumb, a right-hand detail panel, whatever you have.
|
|
279
|
+
|
|
280
|
+
```svelte
|
|
281
|
+
<script lang="ts">
|
|
282
|
+
import {
|
|
283
|
+
createFeedbackBoard,
|
|
284
|
+
createAuthTokenGetter,
|
|
285
|
+
FeedbackSidebar,
|
|
286
|
+
FeedbackList,
|
|
287
|
+
FeedbackDetail,
|
|
288
|
+
FeedbackSubmitForm,
|
|
289
|
+
type BoardItem,
|
|
290
|
+
} from '@alliance-droid/status-feedback-system';
|
|
291
|
+
|
|
292
|
+
let { data } = $props();
|
|
293
|
+
const board = createFeedbackBoard({
|
|
294
|
+
apiUrl: '/svc/feedback',
|
|
295
|
+
userEmail: data.user?.email,
|
|
296
|
+
getAuthToken: createAuthTokenGetter(), // optional — enables status management
|
|
297
|
+
categoryConfig: {
|
|
298
|
+
Bug: { icon: 'fa-solid fa-bug', label: 'Bugs' },
|
|
299
|
+
'Feature Request': { icon: 'fa-solid fa-lightbulb', label: 'Ideas' },
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
let selected = $state<BoardItem | null>(null);
|
|
304
|
+
</script>
|
|
305
|
+
|
|
306
|
+
<YourSidebar><FeedbackSidebar {board} /></YourSidebar>
|
|
307
|
+
<YourMain><FeedbackList {board} onSelect={(i) => (selected = i)} /></YourMain>
|
|
308
|
+
<YourRail>
|
|
309
|
+
{#if selected}<FeedbackDetail {board} item={selected} />{/if}
|
|
310
|
+
</YourRail>
|
|
311
|
+
<!-- Render <FeedbackSubmitForm {board} /> wherever you want submission. -->
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
`createFeedbackBoard(options)` returns a reactive `FeedbackBoardState` (items, categories, filters, voting, `loadTransitions`/`setStatus`, …) shared by every sub-component.
|
|
315
|
+
|
|
316
|
+
| Option | Type | Default | Description |
|
|
317
|
+
|--------|------|---------|-------------|
|
|
318
|
+
| `apiUrl` | `string` | required | Proxy URL or direct admin backend URL |
|
|
319
|
+
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
320
|
+
| `userEmail` | `string` | — | Authenticated user (required to submit feedback/replies) |
|
|
321
|
+
| `categoryConfig` | `Record<string, { label?, icon?, description? }>` | `{}` | Relabel / add icons to sidebar categories |
|
|
322
|
+
| `getAuthToken` | `() => string \| Promise<string \| null>` | — | Bearer token so privileged users can set status (see below) |
|
|
323
|
+
| `pageSize` | `number` | `50` | Items per page |
|
|
324
|
+
|
|
325
|
+
Exported parts: `createFeedbackBoard`, `FeedbackSidebar`, `FeedbackList`, `FeedbackItem`, `FeedbackDetail`, `FeedbackSubmitForm`.
|
|
326
|
+
|
|
327
|
+
Categories are fixed by the system (**Bug**, **Feature Request**) — `categoryConfig` only relabels or adds icons.
|
|
328
|
+
|
|
329
|
+
#### Setting status (ProductOwners and Admins)
|
|
330
|
+
|
|
331
|
+
When you pass `getAuthToken`, **Admins and ProductOwners of the app configured for the feedback project** can accept feedback, sign off **QA / UAT → Ready for Production**, reopen it, or decline it (Won't do, Duplicate, Can't reproduce), where the current status permits the decision. No feedback-admin login or per-user assignment is needed. Reopens and declines require a reason. **Only acceptance starts automation**; engineering, release, and completion transitions remain in the admin app.
|
|
332
|
+
|
|
333
|
+
Editable items have **purple status controls**, while rows keep the consuming app's normal border color. A **Needs your input** cue appears only when a manager's action is required. The sidebar's **Needs my input** filter shows Open, Considering, Waiting on reply, and QA / UAT, filtered on the server across all pages. Everyone continues to see each item's granular status.
|
|
334
|
+
|
|
335
|
+
Deploy the matching admin-server capabilities update first. The packaged proxy forwards the optional board bearer token; custom proxies must also forward `Authorization` on board reads and preserve `status=needs_input`. Custom lists can use the backend's per-item `canChangeStatus` and `needsInput` fields with `board.canManageStatus`.
|
|
336
|
+
|
|
337
|
+
- `getAuthToken` must return the user's **Alliance OIDC access token** whose audience covers **`status-feedback-admin`** (an *id token*, whose `aud` is your app's client id, is rejected). It's called on demand; return `null` for anonymous / non-privileged users and the control simply won't appear — the badge stays a plain read-only badge.
|
|
338
|
+
- **Use the built-in `createAuthTokenGetter()`** for this — it fetches a same-origin token endpoint (`/auth/access-token` by default), caches until just before expiry, single-flights, and returns `null` on any failure. With `@alliance-droid/svelte-auth-core`, set `enableAccessTokenEndpoint: true` and request the `status-feedback-admin` scope so that endpoint exists. Create one instance at module level: `export const getAuthToken = createAuthTokenGetter();`
|
|
339
|
+
- The token travels as `Authorization: Bearer …` to the admin backend, which **verifies it against the IdP** and derives the user's app roles. Authorization (app Admin/Administrator/ProductOwner role + the project's configured OAuth client binding + the restricted board transitions, including reason prompts on declines/reopens) is enforced **server-side** — the client never decides permissions. The backend returns a `canManage` flag (HTTP 200, not 403) so ordinary signed-in users get no console noise.
|
|
340
|
+
- The available transitions are returned by the backend per item, so the menu always reflects exactly what that user may do.
|
|
341
|
+
|
|
342
|
+
> Security note: use the SvelteKit proxy to keep the project `apiKey` out of browser-delivered code. Direct admin calls with an embedded key are still supported for existing integrations, but same-origin proxy mode is the recommended default.
|
|
343
|
+
|
|
344
|
+
### DevFeedback
|
|
345
|
+
|
|
346
|
+
Feedback overlay for authorized users. Activated via **Ctrl+right-click** (Cmd+right-click on macOS) anywhere on the page.
|
|
347
|
+
|
|
348
|
+
Auto-captures: screenshots (via html2canvas), console errors, viewport size, current route, clicked element selector, and deployment context (Vercel env vars).
|
|
349
|
+
|
|
350
|
+
**Gating:** the widget shows only when `enabled` is `true` **and** `userEmail` is non-empty. `enabled` is the single gate — wire it to your own rule, typically a role/permission check. There is no environment auto-detection. See [docs/role-based-access.md](docs/role-based-access.md) for the recommended role-based setup and the pitfalls.
|
|
351
|
+
|
|
352
|
+
```svelte
|
|
353
|
+
<script>
|
|
354
|
+
import { DevFeedback } from '@alliance-droid/status-feedback-system';
|
|
355
|
+
</script>
|
|
356
|
+
|
|
357
|
+
<!-- In your root +layout.svelte -->
|
|
358
|
+
<DevFeedback
|
|
359
|
+
apiUrl="/svc/feedback/dev"
|
|
360
|
+
boardUrl="/feedback"
|
|
361
|
+
userEmail={user?.email ?? ''}
|
|
362
|
+
enabled={userCanGiveFeedback}
|
|
363
|
+
/>
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
| Prop | Type | Default | Description |
|
|
367
|
+
|------|------|---------|-------------|
|
|
368
|
+
| `apiUrl` | `string` | required | Proxy URL or direct admin backend URL. Use `/svc/feedback/dev` with the package proxy for stricter widget policy. |
|
|
369
|
+
| `apiKey` | `string` | — | Project API key; omit when using the same-origin proxy |
|
|
370
|
+
| `enabled` | `boolean` | required | The only gate — wire to your role/permission check |
|
|
371
|
+
| `userEmail` | `string` | required | Signed-in user's email. Empty string ⇒ renders nothing (anonymous feedback unsupported) |
|
|
372
|
+
| `boardUrl` | `string` | — | URL for "View Open Issues" link. Hidden if omitted. |
|
|
373
|
+
| `maxConsoleErrors` | `number` | `20` | Max console errors to capture |
|
|
374
|
+
| `showOnboarding` | `boolean` | `true` | Show "Ctrl+right-click" toast on first visit |
|
|
375
|
+
| `renderTokenEndpoint` | `string` | `/svc/internal/issue-render-token` | App endpoint that mints render tokens for screenshot capture |
|
|
376
|
+
|
|
377
|
+
**Screenshot storage:** If the admin backend has `AZURE_FEEDBACK_BLOB_CONNECTION_STRING` configured, screenshots are uploaded to Azure Blob Storage. Otherwise they're stored as base64 in the database.
|
|
378
|
+
|
|
379
|
+
## API Client
|
|
380
|
+
|
|
381
|
+
All components use a shared API client. You can also use it directly:
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
import { createStatusClient } from '@alliance-droid/status-feedback-system';
|
|
385
|
+
|
|
386
|
+
const client = createStatusClient({
|
|
387
|
+
apiUrl: '/svc/feedback',
|
|
388
|
+
fetch: event.fetch, // Optional: pass SvelteKit's fetch for SSR
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const { systems, overallStatus } = await client.getStatus();
|
|
392
|
+
const { incidents } = await client.getIncidents({ limit: 5, includeResolved: true });
|
|
393
|
+
const { history } = await client.getUptime({ days: 90 });
|
|
394
|
+
const feedback = await client.submitFeedback({ message: 'Great product!' });
|
|
395
|
+
const { items, total, categories } = await client.getBoard({ sort: 'votes', status: 'open' });
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## Shared Components
|
|
399
|
+
|
|
400
|
+
Lower-level building blocks used by StatusPage — exported for custom layouts:
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
import {
|
|
404
|
+
StatusIndicator, // Status dot + label (operational/degraded/outage/maintenance)
|
|
405
|
+
UptimeBar, // 90-day uptime bar chart
|
|
406
|
+
IncidentTimeline, // Chronological incident updates
|
|
407
|
+
SystemCard, // Single system status card with uptime bar
|
|
408
|
+
} from '@alliance-droid/status-feedback-system';
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## All Exports
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
// Components
|
|
415
|
+
import {
|
|
416
|
+
StatusPage, StatusBanner, FeedbackForm, FeedbackButton, DevFeedback,
|
|
417
|
+
StatusIndicator, UptimeBar, IncidentTimeline, SystemCard, PasteImageInput,
|
|
418
|
+
} from '@alliance-droid/status-feedback-system';
|
|
419
|
+
|
|
420
|
+
// Feedback board (composable)
|
|
421
|
+
import {
|
|
422
|
+
createFeedbackBoard,
|
|
423
|
+
createAuthTokenGetter,
|
|
424
|
+
FeedbackSidebar, FeedbackList, FeedbackItem, FeedbackDetail, FeedbackSubmitForm,
|
|
425
|
+
} from '@alliance-droid/status-feedback-system';
|
|
426
|
+
import type {
|
|
427
|
+
FeedbackBoardState, FeedbackBoardOptions, CategoryConfig,
|
|
428
|
+
AuthTokenGetter, AuthTokenGetterOptions,
|
|
429
|
+
} from '@alliance-droid/status-feedback-system';
|
|
430
|
+
|
|
431
|
+
// API Client
|
|
432
|
+
import { createStatusClient } from '@alliance-droid/status-feedback-system';
|
|
433
|
+
import type {
|
|
434
|
+
StatusClient, StatusClientConfig,
|
|
435
|
+
StatusData, IncidentData, UptimeData, BoardData, BoardItem, BoardCategory,
|
|
436
|
+
} from '@alliance-droid/status-feedback-system';
|
|
437
|
+
|
|
438
|
+
// SvelteKit server proxy
|
|
439
|
+
import {
|
|
440
|
+
createSvelteKitFeedbackProxy,
|
|
441
|
+
isFeedbackProxyConfigured,
|
|
442
|
+
} from '@alliance-droid/status-feedback-system/sveltekit/server';
|
|
443
|
+
|
|
444
|
+
// Utilities
|
|
445
|
+
import { deriveOverallStatus } from '@alliance-droid/status-feedback-system';
|
|
446
|
+
|
|
447
|
+
// Types
|
|
448
|
+
import type {
|
|
449
|
+
Project, System, Incident, IncidentUpdate, Feedback, FeedbackResponse,
|
|
450
|
+
FeedbackVisibility, StatusHistoryEntry, ApiResponse,
|
|
451
|
+
SystemStatus, IncidentStatus, IncidentSeverity, FeedbackStatus,
|
|
452
|
+
} from '@alliance-droid/status-feedback-system';
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
## License
|
|
456
|
+
|
|
457
|
+
Copyright © 2026 Alliance Technical Group. All rights reserved.
|