@picked/svelte 0.0.1-dev.1793923852884 → 0.0.1-dev.1793933802654

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 (30) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/plugins/devtools/DevToolsExperience.svelte +32 -2
  3. package/dist/plugins/devtools/derive/guide-sections/guide-sections.d.ts +1 -1
  4. package/dist/plugins/devtools/derive/guide-sections/guide-sections.js +1 -1
  5. package/dist/plugins/devtools/derive/index.d.ts +3 -1
  6. package/dist/plugins/devtools/derive/index.js +1 -1
  7. package/dist/plugins/devtools/derive/issue-report/index.d.ts +3 -0
  8. package/dist/plugins/devtools/derive/issue-report/index.js +1 -0
  9. package/dist/plugins/devtools/derive/issue-report/issue-report.d.ts +255 -0
  10. package/dist/plugins/devtools/derive/issue-report/issue-report.js +2 -0
  11. package/dist/plugins/devtools/derive/plain-language/index.d.ts +1 -1
  12. package/dist/plugins/devtools/derive/plain-language/index.js +1 -1
  13. package/dist/plugins/devtools/derive/plain-language/plain-language.d.ts +6 -0
  14. package/dist/plugins/devtools/derive/plain-language/plain-language.js +1 -1
  15. package/dist/plugins/devtools/index.d.ts +4 -1
  16. package/dist/plugins/devtools/index.js +1 -1
  17. package/dist/plugins/devtools/options.d.ts +30 -1
  18. package/dist/plugins/devtools/options.js +1 -1
  19. package/dist/plugins/devtools/review/index.d.ts +3 -2
  20. package/dist/plugins/devtools/review/index.js +1 -1
  21. package/dist/plugins/devtools/review/issues/ReportIssuePanel.svelte +805 -0
  22. package/dist/plugins/devtools/review/issues/capture-screenshot.d.ts +38 -0
  23. package/dist/plugins/devtools/review/issues/capture-screenshot.js +1 -0
  24. package/dist/plugins/devtools/review/issues/read-attachment.d.ts +27 -0
  25. package/dist/plugins/devtools/review/issues/read-attachment.js +1 -0
  26. package/dist/plugins/devtools/review/issues/site-endpoint.d.ts +17 -0
  27. package/dist/plugins/devtools/review/issues/site-endpoint.js +1 -0
  28. package/dist/plugins/devtools/sidebar/nav.d.ts +6 -3
  29. package/dist/plugins/devtools/sidebar/nav.js +1 -1
  30. package/package.json +2 -2
@@ -0,0 +1,255 @@
1
+ /**
2
+ * @file A reviewer's report of an issue: what it says, the session it was filed from, and the
3
+ * markdown body a tracker shows. A sink maps the report onto its tracker.
4
+ */
5
+ import type { JsonValue } from '@picked/client';
6
+ /** What a report is about. */
7
+ export type IssueKind = 'bug' | 'improvement' | 'feature';
8
+ /** Every kind, in the order the form offers them. */
9
+ export declare const ISSUE_KINDS: readonly {
10
+ value: IssueKind;
11
+ label: string;
12
+ }[];
13
+ /** How soon a report needs attention. A sink maps it onto its tracker's own scale. */
14
+ export type IssuePriority = 'none' | 'low' | 'medium' | 'high' | 'urgent';
15
+ /** Every priority, lowest first, in the order the form offers them. */
16
+ export declare const ISSUE_PRIORITIES: readonly {
17
+ value: IssuePriority;
18
+ label: string;
19
+ }[];
20
+ /** A file sent with a report. */
21
+ export interface IssueAttachment {
22
+ filename: string;
23
+ contentType: string;
24
+ /** The file's bytes, base64-encoded, with no `data:` prefix. */
25
+ data: string;
26
+ /** The file's size in bytes. */
27
+ size: number;
28
+ }
29
+ /** Where the visitor was when a report was made. Versioned, so a reader can rebuild the session. */
30
+ export interface IssueContext {
31
+ v: 1;
32
+ /** When the context was taken, as an ISO 8601 time. */
33
+ capturedAt: string;
34
+ /** The page's path, without its host or query. Empty once the reviewer removes it. */
35
+ pagePath: string;
36
+ /** Which configuration the page runs: the published one, or a preview's draft. */
37
+ workspace: 'published' | 'draft';
38
+ experience: {
39
+ key: string;
40
+ name: string;
41
+ } | null;
42
+ step: {
43
+ key: string;
44
+ name: string;
45
+ inTransit: boolean;
46
+ } | null;
47
+ /** The steps moved on from, oldest first. */
48
+ route: {
49
+ experienceKey: string;
50
+ stepKey: string;
51
+ stepName: string;
52
+ }[];
53
+ /** The responses, by field key, with personal values hidden. */
54
+ responses: Record<string, JsonValue>;
55
+ /** The keys whose values `responses` hides. */
56
+ hidden: string[];
57
+ /** The persona the reviewer walks through as. */
58
+ persona: {
59
+ id: string;
60
+ name: string;
61
+ } | null;
62
+ /** Facts the site adds, such as a scenario key. */
63
+ site: Record<string, JsonValue>;
64
+ }
65
+ /** What a reviewer reports, ready for a sink. */
66
+ export interface IssueReport {
67
+ v: 1;
68
+ title: string;
69
+ description: string;
70
+ kind: IssueKind;
71
+ priority: IssuePriority;
72
+ labels: string[];
73
+ attachments: IssueAttachment[];
74
+ context: IssueContext;
75
+ /** The description, a readable context and the context as a JSON block, in markdown. */
76
+ body: string;
77
+ }
78
+ /** What a sink hands back once a report is filed. */
79
+ export interface IssueReceipt {
80
+ /** The tracker's name for the issue, such as `ENG-123`. */
81
+ reference: string | null;
82
+ /** Where the issue can be opened. */
83
+ url: string | null;
84
+ }
85
+ /** Files a report and resolves with its receipt; rejects when it was not filed. */
86
+ export type IssueSink = (report: IssueReport) => Promise<IssueReceipt>;
87
+ /** What a hidden response reads as. */
88
+ export declare const HIDDEN_VALUES: {
89
+ readonly email: '[hidden email]';
90
+ readonly phone: '[hidden phone number]';
91
+ readonly date: '[hidden date]';
92
+ readonly details: '[hidden details]';
93
+ };
94
+ /** The longest title a report takes. */
95
+ export declare const MAX_TITLE_LENGTH = 256;
96
+ /** The longest description a report takes. */
97
+ export declare const MAX_DESCRIPTION_LENGTH = 10000;
98
+ /** File types a report can carry: raster images, PDF and plain text, none of which runs script. */
99
+ export declare const ATTACHMENT_TYPES: readonly string[];
100
+ /** The most a report carries in files, each one and all together. */
101
+ export declare const MAX_ATTACHMENT_BYTES: number;
102
+ /**
103
+ * A response as a report shows it by default. Text that reads as an email, a phone number or a
104
+ * date is hidden, and so is anything structured, which tends to hold a person's details.
105
+ *
106
+ * @param {unknown} value - The response.
107
+ * @returns {JsonValue | undefined} The value to send, or `undefined` for one that cannot be sent.
108
+ * @example maskValue({ name: 'Ann' }) // '[hidden details]'
109
+ */
110
+ export declare function maskValue(value: unknown): JsonValue | undefined;
111
+ /**
112
+ * Whether a value is one a report can carry as JSON.
113
+ *
114
+ * @param {unknown} value - The value to test.
115
+ * @returns {boolean} `true` for text, finite numbers, booleans, `null`, and arrays or plain objects of those.
116
+ * @example isJsonValue({ a: [1, 'b'] }) // true
117
+ */
118
+ export declare function isJsonValue(value: unknown): value is JsonValue;
119
+ /** What a report's context is built from. */
120
+ export interface ContextInput {
121
+ capturedAt: Date;
122
+ pagePath: string;
123
+ workspace: 'published' | 'draft';
124
+ /** The journey in front of the reviewer. */
125
+ journey: {
126
+ experienceKey: string;
127
+ experienceName: string;
128
+ stepKey: string | null;
129
+ stepName: string | null;
130
+ inTransit: boolean;
131
+ } | null;
132
+ /** The steps moved on from, oldest first. */
133
+ route: readonly {
134
+ journey: string;
135
+ screen: string;
136
+ screenName: string;
137
+ }[];
138
+ responses: Readonly<Record<string, unknown>>;
139
+ /** Keys that code commits, which a report leaves out. */
140
+ codeOwned: ReadonlySet<string>;
141
+ persona: {
142
+ id: string;
143
+ name: string;
144
+ } | null;
145
+ site: Readonly<Record<string, unknown>>;
146
+ /**
147
+ * Decides each response's value. An answer that is not JSON, such as `undefined`, leaves the
148
+ * response out. Defaults to {@link maskValue}.
149
+ */
150
+ redact: ((key: string, value: unknown) => unknown) | null;
151
+ }
152
+ /**
153
+ * Builds a report's context: where the visitor is, how they got there, and their responses with
154
+ * personal values hidden.
155
+ *
156
+ * @param {ContextInput} input - The session, as the devtools read it.
157
+ * @returns {IssueContext} The context.
158
+ * @example buildIssueContext({ ...input, responses: { email: 'ann@example.com' } }).hidden // ['email']
159
+ */
160
+ export declare function buildIssueContext(input: ContextInput): IssueContext;
161
+ /** A part of the context the reviewer can see, and remove before sending. */
162
+ export type ContextRowId = 'experience' | 'step' | 'page' | 'route' | 'responses' | 'persona' | 'site';
163
+ /** One line of the context a report carries. */
164
+ export interface ContextRow {
165
+ id: ContextRowId;
166
+ label: string;
167
+ value: string;
168
+ }
169
+ /**
170
+ * The parts of a context, as lines a reviewer reads. A part with nothing in it has no line.
171
+ *
172
+ * @param {IssueContext} context - The context.
173
+ * @returns {ContextRow[]} The lines, in reading order.
174
+ * @example contextRows(context)[0] // { id: 'experience', label: 'Experience', value: 'Funnel' }
175
+ */
176
+ export declare function contextRows(context: IssueContext): ContextRow[];
177
+ /**
178
+ * A set with a value added, or taken out when it is already there.
179
+ *
180
+ * @param {ReadonlySet<T>} set - The set.
181
+ * @param {T} value - The value to toggle.
182
+ * @returns {ReadonlySet<T>} A new set; the given one is left as it is.
183
+ * @template T
184
+ * @example toggled(new Set(['a']), 'a').size // 0
185
+ */
186
+ export declare function toggled<T>(set: ReadonlySet<T>, value: T): ReadonlySet<T>;
187
+ /**
188
+ * A context without the parts the reviewer removed.
189
+ *
190
+ * @param {IssueContext} context - The context.
191
+ * @param {ReadonlySet<ContextRowId>} removed - The parts to leave out.
192
+ * @returns {IssueContext} The context, emptied of each removed part.
193
+ * @example withoutRows(context, new Set(['responses'])).responses // {}
194
+ */
195
+ export declare function withoutRows(context: IssueContext, removed: ReadonlySet<ContextRowId>): IssueContext;
196
+ /**
197
+ * A report's markdown body: the description, the context as lines, then the context as a JSON
198
+ * block a reader can rebuild the session from.
199
+ *
200
+ * @param {string} description - What the reviewer wrote.
201
+ * @param {IssueContext} context - The context the report carries.
202
+ * @returns {string} The body.
203
+ * @example renderIssueBody('The button is grey.', context).startsWith('The button is grey.') // true
204
+ */
205
+ export declare function renderIssueBody(description: string, context: IssueContext): string;
206
+ /** What the reviewer filled in. */
207
+ export interface IssueDraft {
208
+ title: string;
209
+ description: string;
210
+ kind: IssueKind;
211
+ priority: IssuePriority;
212
+ labels: readonly string[];
213
+ attachments: readonly IssueAttachment[];
214
+ }
215
+ /**
216
+ * Why a draft cannot be sent yet, or `null` when it can.
217
+ *
218
+ * @param {Pick<IssueDraft, 'title' | 'description'>} draft - The draft.
219
+ * @returns {string | null} The problem, in words the reviewer reads.
220
+ * @example draftProblem({ title: '', description: '' }) // 'Give the issue a title.'
221
+ */
222
+ export declare function draftProblem(draft: Pick<IssueDraft, 'title' | 'description'>): string | null;
223
+ /**
224
+ * Builds the report a sink files.
225
+ *
226
+ * @param {IssueDraft} draft - What the reviewer filled in.
227
+ * @param {IssueContext} context - The context, without the parts the reviewer removed.
228
+ * @returns {IssueReport} The report, with its markdown body.
229
+ * @example buildIssueReport(draft, context).body
230
+ */
231
+ export declare function buildIssueReport(draft: IssueDraft, context: IssueContext): IssueReport;
232
+ /**
233
+ * Why a file cannot go with a report, or `null` when it can.
234
+ *
235
+ * @param {{ name: string; type: string; size: number }} file - The file.
236
+ * @param {string} file.name - Its name.
237
+ * @param {string} file.type - Its media type.
238
+ * @param {number} file.size - Its size in bytes.
239
+ * @param {readonly IssueAttachment[]} attached - The files already attached.
240
+ * @returns {string | null} The problem, in words the reviewer reads.
241
+ * @example attachmentProblem({ name: 'logo.svg', type: 'image/svg+xml', size: 10 }, [])
242
+ */
243
+ export declare function attachmentProblem(file: {
244
+ name: string;
245
+ type: string;
246
+ size: number;
247
+ }, attached: readonly IssueAttachment[]): string | null;
248
+ /**
249
+ * Reads a sink's answer as a receipt. A sink that answers nothing usable still filed the report.
250
+ *
251
+ * @param {unknown} value - What the sink resolved with.
252
+ * @returns {IssueReceipt} The reference and link it named, each `null` when absent.
253
+ * @example readReceipt({ identifier: 'ENG-1', url: 'https://…' }) // { reference: 'ENG-1', url: 'https://…' }
254
+ */
255
+ export declare function readReceipt(value: unknown): IssueReceipt;
@@ -0,0 +1,2 @@
1
+ var e=[{value:`bug`,label:`Bug`},{value:`improvement`,label:`Improvement`},{value:`feature`,label:`Feature`}],t=[{value:`none`,label:`No priority`},{value:`low`,label:`Low`},{value:`medium`,label:`Medium`},{value:`high`,label:`High`},{value:`urgent`,label:`Urgent`}],n={email:`[hidden email]`,phone:`[hidden phone number]`,date:`[hidden date]`,details:`[hidden details]`},r=256,i=1e4,a=[`image/png`,`image/jpeg`,`image/gif`,`image/webp`,`application/pdf`,`text/plain`,`text/csv`,`application/json`],o=3145728,s=/^\+?[\d\s().-]+$/,c=/^\d{1,4}[-/.]\d{1,2}[-/.]\d{1,4}/;function l(e){return[...e].filter(e=>e>=`0`&&e<=`9`).length}function u(e){let t=e.split(`@`);if(t.length!==2||/\s/.test(e))return!1;let[n=``,r=``]=t,i=r.indexOf(`.`);return n!==``&&i>0&&i<r.length-1}function d(e){let t=e.trim();return u(t)?n.email:c.test(t)?n.date:s.test(t)&&l(t)>=8?n.phone:e}function f(e){if(e===null||typeof e==`boolean`)return e;if(typeof e==`number`)return Number.isFinite(e)?e:void 0;if(typeof e==`string`)return d(e);if(Array.isArray(e)){let t=e.map(e=>typeof e==`object`?void 0:f(e));return t.every(e=>e!==void 0)?t:n.details}return typeof e==`object`?n.details:void 0}function p(e){return e===null||typeof e==`string`||typeof e==`boolean`?!0:typeof e==`number`?Number.isFinite(e):Array.isArray(e)?e.every(p):typeof e==`object`&&Object.values(e).every(p)}function m(e){let t={},n=[];for(let[r,i]of Object.entries(e.responses)){if(e.codeOwned.has(r))continue;let a=e.redact?e.redact(r,i):f(i);p(a)&&(t[r]=a,JSON.stringify(a)!==JSON.stringify(i)&&n.push(r))}let r={};for(let[t,n]of Object.entries(e.site))p(n)&&(r[t]=n);let{journey:i}=e;return{v:1,capturedAt:e.capturedAt.toISOString(),pagePath:e.pagePath,workspace:e.workspace,experience:i?{key:i.experienceKey,name:i.experienceName}:null,step:i?.stepKey==null?null:{key:i.stepKey,name:i.stepName??i.stepKey,inTransit:i.inTransit},route:e.route.map(e=>({experienceKey:e.journey,stepKey:e.screen,stepName:e.screenName})),responses:t,hidden:n,persona:e.persona,site:r}}function h(e){return typeof e==`string`?e:JSON.stringify(e)}function g(e){let t=[];if(e.experience&&t.push({id:`experience`,label:`Experience`,value:e.experience.name}),e.step){let n=e.step.inTransit?` (moving on from it)`:``;t.push({id:`step`,label:`Step`,value:`${e.step.name}${n}`})}if(e.pagePath){let n=e.workspace===`draft`?`, draft configuration`:``;t.push({id:`page`,label:`Page`,value:`${e.pagePath}${n}`})}return t}function _(e){let t=[];if(e.route.length>0){let n=e.route.length===1?`1 step`:`${e.route.length} steps`;t.push({id:`route`,label:`Route`,value:`${n} before this one`})}let n=Object.keys(e.responses).length;if(n>0){let r=n===1?`1 response`:`${n} responses`,i=e.hidden.length>0?`, ${e.hidden.length} hidden`:``;t.push({id:`responses`,label:`Responses`,value:`${r}${i}`})}e.persona&&t.push({id:`persona`,label:`Persona`,value:e.persona.name});let r=Object.entries(e.site);if(r.length>0){let e=r.map(([e,t])=>`${e}: ${h(t)}`).join(`, `);t.push({id:`site`,label:`Site`,value:e})}return t}function v(e){return[...g(e),..._(e)]}function y(e,t){return e.has(t)?new Set([...e].filter(e=>e!==t)):new Set([...e,t])}function b(e,t){return{...e,experience:t.has(`experience`)?null:e.experience,step:t.has(`step`)?null:e.step,pagePath:t.has(`page`)?``:e.pagePath,route:t.has(`route`)?[]:e.route,responses:t.has(`responses`)?{}:e.responses,hidden:t.has(`responses`)?[]:e.hidden,persona:t.has(`persona`)?null:e.persona,site:t.has(`site`)?{}:e.site}}function x(e,t){let n=e.trim(),r=v(t).map(e=>`- **${e.label}:** ${e.value}`);return[...n?[n,``,`---`,``]:[],`### Context`,``,...r.length>0?r:[`- Nothing about the session was included.`],`- **Captured:** ${t.capturedAt}`,``,`**Debug context:**`,``,"```json",JSON.stringify(t,null,2),"```"].join(`
2
+ `)}function S(e){return e.title.trim()===``?`Give the issue a title.`:e.title.length>256?`Keep the title under 256 characters.`:e.description.length>1e4?`Keep the description under ${i.toLocaleString(`en`)} characters.`:null}function C(e,t){return{v:1,title:e.title.trim(),description:e.description.trim(),kind:e.kind,priority:e.priority,labels:[...new Set(e.labels.map(e=>e.trim()).filter(Boolean))],attachments:[...e.attachments],context:t,body:x(e.description,t)}}function w(e,t){return a.includes(e.type)?e.size>3145728?`"${e.name}" is over 3 MB.`:t.reduce((e,t)=>e+t.size,e.size)>3145728?`A report carries at most 3 MB of files.`:null:`"${e.name}" can't go with a report. Attach an image, a PDF, or a text, CSV or JSON file.`}function T(e){if(typeof e!=`object`||!e)return{reference:null,url:null};let{reference:t,identifier:n,id:r,url:i}=Object.fromEntries(Object.entries(e)),a=[t,n,r].find(e=>typeof e==`string`);return{reference:typeof a==`string`&&a!==``?a:null,url:typeof i==`string`&&/^https?:\/\//.test(i)?i:null}}export{a as ATTACHMENT_TYPES,n as HIDDEN_VALUES,e as ISSUE_KINDS,t as ISSUE_PRIORITIES,o as MAX_ATTACHMENT_BYTES,i as MAX_DESCRIPTION_LENGTH,r as MAX_TITLE_LENGTH,w as attachmentProblem,m as buildIssueContext,C as buildIssueReport,v as contextRows,S as draftProblem,p as isJsonValue,f as maskValue,T as readReceipt,x as renderIssueBody,y as toggled,b as withoutRows};
@@ -1,3 +1,3 @@
1
1
  /** @file Public entrypoint for the review tools' vocabulary contract. */
2
- export { NODE_LABELS, TAB_INTROS, ADVANCED_LABEL, TAB_LABELS, configSourceLabel, deslug, screenName, } from './plain-language';
2
+ export { NODE_LABELS, TAB_INTROS, ADVANCED_LABEL, REPORT_ISSUE_INTRO, REPORT_ISSUE_LABEL, REPORT_ISSUE_TAB, TAB_LABELS, configSourceLabel, deslug, screenName, } from './plain-language';
3
3
  export type { ReviewTabId } from './plain-language';
@@ -1 +1 @@
1
- export{ADVANCED_LABEL,NODE_LABELS,TAB_INTROS,TAB_LABELS,configSourceLabel,deslug,screenName}from"./plain-language.js";
1
+ export{ADVANCED_LABEL,NODE_LABELS,REPORT_ISSUE_INTRO,REPORT_ISSUE_LABEL,REPORT_ISSUE_TAB,TAB_INTROS,TAB_LABELS,configSourceLabel,deslug,screenName}from"./plain-language.js";
@@ -61,6 +61,12 @@ export declare const TAB_LABELS: {
61
61
  export type ReviewTabId = keyof typeof TAB_LABELS;
62
62
  /** The name of the folding sidebar group that holds the SDK's own screens. */
63
63
  export declare const ADVANCED_LABEL = "Advanced";
64
+ /** The id of the tab the bug icon at the sidebar's foot opens. */
65
+ export declare const REPORT_ISSUE_TAB = "report-issue";
66
+ /** The Report an issue tab's name. */
67
+ export declare const REPORT_ISSUE_LABEL = "Report an issue";
68
+ /** The Report an issue tab's one-sentence purpose. */
69
+ export declare const REPORT_ISSUE_INTRO = "Tell the team what is wrong or what to change. Where you are in the experience goes with it.";
64
70
  /** The one-sentence purpose each tab leads with. Kept together so the tone stays consistent. */
65
71
  export declare const TAB_INTROS: {
66
72
  readonly guide: 'How to use each tab. Open a tab straight from its section.';
@@ -1 +1 @@
1
- var e={origin:`Origin`,step:`Step`,condition:`Condition`,action:`Action`,experience:`Opens another experience`};function t(e){let t=e.replace(/[-_]+/g,` `).trim();return t.length===0?e:t.charAt(0).toUpperCase()+t.slice(1)}function n(e,n){let r=e?.trim();return r!==void 0&&r.length>0?r:n!==void 0&&n.length>0?t(n):`Untitled step`}function r(e){return e!==void 0&&e.length>0?`Viewing unpublished changes`:`Viewing published content`}var i={guide:`How to use`,session:`This session`,answers:`Responses`,journey:`Experiences`,personas:`Personas & Scenarios`,variations:`Feature flags`},a=`Advanced`,o={guide:`How to use each tab. Open a tab straight from its section.`,session:`Where you are right now, and your responses on each step. Start here if something looks wrong.`,answers:`Every field this experience can ask for, and the responses given. Change one to see what the visitor would see.`,journey:`Every experience on this site: the route each can take, with yours marked, and where and when it shows. Go back to any step you have already visited.`,personas:`Visitors and situations to try this experience as. Walk through as one, or jump to a step on its route.`,variations:`The feature flags running on this site. Force a variant to review it, then copy the link to show someone else exactly what you saw.`};export{a as ADVANCED_LABEL,e as NODE_LABELS,o as TAB_INTROS,i as TAB_LABELS,r as configSourceLabel,t as deslug,n as screenName};
1
+ var e={origin:`Origin`,step:`Step`,condition:`Condition`,action:`Action`,experience:`Opens another experience`};function t(e){let t=e.replace(/[-_]+/g,` `).trim();return t.length===0?e:t.charAt(0).toUpperCase()+t.slice(1)}function n(e,n){let r=e?.trim();return r!==void 0&&r.length>0?r:n!==void 0&&n.length>0?t(n):`Untitled step`}function r(e){return e!==void 0&&e.length>0?`Viewing unpublished changes`:`Viewing published content`}var i={guide:`How to use`,session:`This session`,answers:`Responses`,journey:`Experiences`,personas:`Personas & Scenarios`,variations:`Feature flags`},a=`Advanced`,o=`report-issue`,s=`Report an issue`,c=`Tell the team what is wrong or what to change. Where you are in the experience goes with it.`,l={guide:`How to use each tab. Open a tab straight from its section.`,session:`Where you are right now, and your responses on each step. Start here if something looks wrong.`,answers:`Every field this experience can ask for, and the responses given. Change one to see what the visitor would see.`,journey:`Every experience on this site: the route each can take, with yours marked, and where and when it shows. Go back to any step you have already visited.`,personas:`Visitors and situations to try this experience as. Walk through as one, or jump to a step on its route.`,variations:`The feature flags running on this site. Force a variant to review it, then copy the link to show someone else exactly what you saw.`};export{a as ADVANCED_LABEL,e as NODE_LABELS,c as REPORT_ISSUE_INTRO,s as REPORT_ISSUE_LABEL,o as REPORT_ISSUE_TAB,l as TAB_INTROS,i as TAB_LABELS,r as configSourceLabel,t as deslug,n as screenName};
@@ -1,8 +1,11 @@
1
- /** @file Public entrypoint for the Svelte devtools plugin — the plugin factory, session access and consumer-facing types. */
1
+ /** @file Public entrypoint for the Svelte devtools plugin — the plugin factory, session access, the issue sinks and consumer-facing types. */
2
2
  export { svelteDevtoolsPlugin } from './plugin';
3
3
  export { getDevtoolsSession } from './session-access';
4
+ export { siteEndpoint } from './review/issues/site-endpoint';
5
+ export type { SiteEndpointOptions } from './review/issues/site-endpoint';
4
6
  export type * from './plugin';
5
7
  export type * from './options';
6
8
  export type { CustomTool, CustomToolGuide, CustomToolIcon, CustomToolPlacement, } from './customTools';
7
9
  export type { DevtoolsSession } from './session-access';
8
10
  export type { PersonaDefinition, PersonaSetup } from './derive/personas';
11
+ export type { IssueAttachment, IssueContext, IssueKind, IssuePriority, IssueReceipt, IssueReport, IssueSink, } from './derive/issue-report';
@@ -1 +1 @@
1
- import{svelteDevtoolsPlugin as e}from"./plugin.js";import{getDevtoolsSession as t}from"./session-access.js";export{t as getDevtoolsSession,e as svelteDevtoolsPlugin};
1
+ import{svelteDevtoolsPlugin as e}from"./plugin.js";import{getDevtoolsSession as t}from"./session-access.js";import{siteEndpoint as n}from"./review/issues/site-endpoint.js";export{t as getDevtoolsSession,n as siteEndpoint,e as svelteDevtoolsPlugin};
@@ -1,5 +1,6 @@
1
1
  /** @file The devtools plugin's options, and how the devtools panel reads them back off the client. */
2
- import type { FieldValue, Picked } from '@picked/client';
2
+ import type { FieldValue, JsonValue, Picked } from '@picked/client';
3
+ import { type IssueContext, type IssueReceipt, type IssueReport, type IssueSink } from './derive/issue-report';
3
4
  import { type Persona, type PersonaDefinition } from './derive/personas';
4
5
  export { DEVTOOLS_EXPERIENCE_KEY } from './constants';
5
6
  /** The corner the floating devtools button sits in. */
@@ -69,6 +70,27 @@ export interface DevtoolsGuide {
69
70
  sections?: readonly DevtoolsGuideSection[];
70
71
  fixes?: readonly DevtoolsGuideFix[];
71
72
  }
73
+ /** How Report an issue files reports, and what each one carries. */
74
+ export interface IssueReportingOptions {
75
+ /** Files a report, such as `siteEndpoint('/api/report-issue')` posting it to the site's server. */
76
+ sink: IssueSink;
77
+ /** Labels each report starts with, from its context. The reviewer can drop any of them. */
78
+ labels?: (context: IssueContext) => readonly string[];
79
+ /** Facts about the site each report carries, such as a scenario key. */
80
+ context?: (client: Picked) => Readonly<Record<string, JsonValue>>;
81
+ /**
82
+ * Decides each response's value in a report; `undefined` leaves the response out. Without it,
83
+ * text that reads as an email, a phone number or a date is hidden, and so is anything structured.
84
+ */
85
+ redact?: (key: string, value: unknown) => JsonValue | undefined;
86
+ }
87
+ /** The Report an issue options, with every site hook guarded. */
88
+ export interface ResolvedIssueOptions {
89
+ sink: (report: IssueReport) => Promise<IssueReceipt>;
90
+ labels: (context: IssueContext) => string[];
91
+ context: (client: Picked) => Record<string, unknown>;
92
+ redact: ((key: string, value: unknown) => unknown) | null;
93
+ }
72
94
  /** The options read by the devtools panel, as passed to `svelteDevtoolsPlugin`. */
73
95
  export interface DevtoolsPanelOptions {
74
96
  /**
@@ -104,6 +126,11 @@ export interface DevtoolsPanelOptions {
104
126
  personas?: readonly PersonaDefinition[] | (() => Promise<readonly PersonaDefinition[]>);
105
127
  /** How loading a persona walks an experience. */
106
128
  personaWalk?: PersonaWalkOptions;
129
+ /**
130
+ * Where Report an issue sends reports. Without it, the bug icon at the sidebar's foot opens a
131
+ * page that says how to set reports up.
132
+ */
133
+ issues?: IssueReportingOptions;
107
134
  }
108
135
  /** The devtools options with every default applied. */
109
136
  export interface ResolvedDevtoolsOptions {
@@ -126,6 +153,8 @@ export interface ResolvedDevtoolsOptions {
126
153
  responses: Readonly<Record<string, FieldValue>>;
127
154
  handoffs: Readonly<Record<string, string>>;
128
155
  };
156
+ /** `null` while the site has not set reports up. */
157
+ issues: ResolvedIssueOptions | null;
129
158
  }
130
159
  /** Every corner the floating button can sit in. */
131
160
  export declare const LAUNCHER_POSITIONS: readonly LauncherPosition[];
@@ -1 +1 @@
1
- import{DEVTOOLS_EXPERIENCE_KEY as e,DEVTOOLS_PLUGIN_NAME as t}from"./constants.js";import{readAnswers as n,sitePersonas as r}from"./derive/personas/personas.js";var i=[`top-left`,`top-right`,`bottom-left`,`bottom-right`];function a(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function o(e){return Array.isArray(e)?e.filter(e=>typeof e==`string`):[]}function s(e){if(!a(e))return{};let t={};for(let[n,r]of Object.entries(e))typeof r==`string`&&(t[n]=r);return t}function c(e){return i.some(t=>t===e)}function l(e){if(!a(e))return null;let{id:t,title:n,steps:r,notes:i}=e;return typeof t!=`string`||typeof n!=`string`||!Array.isArray(r)?null:{id:t,title:n,steps:o(r),notes:o(i)}}function u(e){if(!a(e))return null;let{problem:t,fix:n}=e;return typeof t!=`string`||typeof n!=`string`?null:{problem:t,fix:n}}function d(e){if(!a(e))return null;let{label:t,run:n}=e;return typeof t!=`string`||typeof n!=`function`?null:{label:t,run:e=>{Reflect.apply(n,void 0,[e])}}}function f(e){if(typeof e!=`function`){let t=r(e);return()=>Promise.resolve(t)}let t=null;return()=>(t??=Promise.resolve().then(()=>Reflect.apply(e,void 0,[])).then(e=>r(e)).catch(()=>[]),t)}function p(r){let i=r.getPluginConfig(t).options,p=a(i)?i:{},m=a(p.guide)?p.guide:{},h=a(p.launcher)?p.launcher:{},g=a(p.personaWalk)?p.personaWalk:{},_=p.environmentLabel;return{readOnly:p.readOnly===!0,environmentLabel:typeof _==`string`&&_.length>0?_:void 0,hiddenExperienceKeys:new Set([e,...o(p.hiddenExperienceKeys)]),experiencePrecedence:o(p.experiencePrecedence),codeOwnedFields:new Set(o(p.codeOwnedFields)),responseFollowUp:d(p.responseFollowUp),guide:{sections:(Array.isArray(m.sections)?m.sections:[]).map(l).filter(e=>e!==null),fixes:(Array.isArray(m.fixes)?m.fixes:[]).map(u).filter(e=>e!==null)},launcher:{position:c(h.position)?h.position:`bottom-right`,autoHide:typeof h.autoHide!=`boolean`||h.autoHide},personas:f(p.personas),personaWalk:{selfAdvancingSteps:new Set(o(g.selfAdvancingSteps)),responses:n(g.responses),handoffs:s(g.handoffs)}}}export{e as DEVTOOLS_EXPERIENCE_KEY,i as LAUNCHER_POSITIONS,p as readDevtoolsOptions};
1
+ import{DEVTOOLS_EXPERIENCE_KEY as e,DEVTOOLS_PLUGIN_NAME as t}from"./constants.js";import{readReceipt as n}from"./derive/issue-report/issue-report.js";import{readAnswers as r,sitePersonas as i}from"./derive/personas/personas.js";var a=[`top-left`,`top-right`,`bottom-left`,`bottom-right`];function o(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function s(e){return Array.isArray(e)?e.filter(e=>typeof e==`string`):[]}function c(e){if(!o(e))return{};let t={};for(let[n,r]of Object.entries(e))typeof r==`string`&&(t[n]=r);return t}function l(e){return a.some(t=>t===e)}function u(e){if(!o(e))return null;let{id:t,title:n,steps:r,notes:i}=e;return typeof t!=`string`||typeof n!=`string`||!Array.isArray(r)?null:{id:t,title:n,steps:s(r),notes:s(i)}}function d(e){if(!o(e))return null;let{problem:t,fix:n}=e;return typeof t!=`string`||typeof n!=`string`?null:{problem:t,fix:n}}function f(e){if(!o(e))return null;let{label:t,run:n}=e;return typeof t!=`string`||typeof n!=`function`?null:{label:t,run:e=>{Reflect.apply(n,void 0,[e])}}}function p(e){if(typeof e!=`function`){let t=i(e);return()=>Promise.resolve(t)}let t=null;return()=>(t??=Promise.resolve().then(()=>Reflect.apply(e,void 0,[])).then(e=>i(e)).catch(()=>[]),t)}function m(e,t){if(typeof e==`function`)try{return Reflect.apply(e,void 0,t)}catch{return}}function h(e){if(!o(e))return null;let{sink:t,labels:r,context:i,redact:a}=e;return typeof t==`function`?{sink:async e=>n(await Reflect.apply(t,void 0,[e])),labels:e=>s(m(r,[e])),context:e=>{let t=m(i,[e]);return o(t)?t:{}},redact:typeof a==`function`?(e,t)=>m(a,[e,t]):null}:null}function g(n){let i=n.getPluginConfig(t).options,a=o(i)?i:{},m=o(a.guide)?a.guide:{},g=o(a.launcher)?a.launcher:{},_=o(a.personaWalk)?a.personaWalk:{},v=a.environmentLabel;return{readOnly:a.readOnly===!0,environmentLabel:typeof v==`string`&&v.length>0?v:void 0,hiddenExperienceKeys:new Set([e,...s(a.hiddenExperienceKeys)]),experiencePrecedence:s(a.experiencePrecedence),codeOwnedFields:new Set(s(a.codeOwnedFields)),responseFollowUp:f(a.responseFollowUp),guide:{sections:(Array.isArray(m.sections)?m.sections:[]).map(u).filter(e=>e!==null),fixes:(Array.isArray(m.fixes)?m.fixes:[]).map(d).filter(e=>e!==null)},launcher:{position:l(g.position)?g.position:`bottom-right`,autoHide:typeof g.autoHide!=`boolean`||g.autoHide},personas:p(a.personas),personaWalk:{selfAdvancingSteps:new Set(s(_.selfAdvancingSteps)),responses:r(_.responses),handoffs:c(_.handoffs)},issues:h(a.issues)}}export{e as DEVTOOLS_EXPERIENCE_KEY,a as LAUNCHER_POSITIONS,g as readDevtoolsOptions};
@@ -1,6 +1,6 @@
1
1
  /**
2
- * @file The review tabs' panels: How to use, This session, Responses, Flow, Personas & Scenarios
3
- * and Feature flags.
2
+ * @file The review tabs' panels: How to use, This session, Responses, Experiences, Personas &
3
+ * Scenarios, Feature flags, and Report an issue.
4
4
  */
5
5
  export { default as GuidePanel } from './guide/GuidePanel.svelte';
6
6
  export { default as SessionPanel } from './session/SessionPanel.svelte';
@@ -8,3 +8,4 @@ export { default as AnswersPanel } from './answers/AnswersPanel.svelte';
8
8
  export { default as JourneyPanel } from './journey/JourneyPanel.svelte';
9
9
  export { default as PersonasPanel } from './personas/PersonasPanel.svelte';
10
10
  export { default as VariationsPanel } from './variations/VariationsPanel.svelte';
11
+ export { default as ReportIssuePanel } from './issues/ReportIssuePanel.svelte';
@@ -1 +1 @@
1
- import e from"./guide/GuidePanel.svelte";import t from"./session/SessionPanel.svelte";import n from"./answers/AnswersPanel.svelte";import r from"./journey/JourneyPanel.svelte";import i from"./personas/PersonasPanel.svelte";import a from"./variations/VariationsPanel.svelte";export{n as AnswersPanel,e as GuidePanel,r as JourneyPanel,i as PersonasPanel,t as SessionPanel,a as VariationsPanel};
1
+ import e from"./guide/GuidePanel.svelte";import t from"./session/SessionPanel.svelte";import n from"./answers/AnswersPanel.svelte";import r from"./journey/JourneyPanel.svelte";import i from"./personas/PersonasPanel.svelte";import a from"./variations/VariationsPanel.svelte";import o from"./issues/ReportIssuePanel.svelte";export{n as AnswersPanel,e as GuidePanel,r as JourneyPanel,i as PersonasPanel,o as ReportIssuePanel,t as SessionPanel,a as VariationsPanel};