@happyvertical/smrt-ingestion 0.57.45 → 0.57.46

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 (79) hide show
  1. package/FEEDBACK.md +30 -0
  2. package/PROPOSALS.md +29 -0
  3. package/REVIEW.md +79 -0
  4. package/agents/execution.md +3 -1
  5. package/dist/chunks/{execution-DKWz2cz0.js → execution-Clcmi0sj.js} +2 -2
  6. package/dist/chunks/{execution-DKWz2cz0.js.map → execution-Clcmi0sj.js.map} +1 -1
  7. package/dist/chunks/{feedback-DHcSKgVU.js → feedback-hDSMYjiG.js} +94 -34
  8. package/dist/chunks/feedback-hDSMYjiG.js.map +1 -0
  9. package/dist/chunks/{proposal-contracts-CPH3ckgH.js → proposal-contracts-DREPLG56.js} +75 -2
  10. package/dist/chunks/proposal-contracts-DREPLG56.js.map +1 -0
  11. package/dist/chunks/{reviewer-instruction-Deoe4NPv.js → reviewer-instruction-BeOYVCVk.js} +2 -2
  12. package/dist/chunks/{reviewer-instruction-Deoe4NPv.js.map → reviewer-instruction-BeOYVCVk.js.map} +1 -1
  13. package/dist/execution-contracts.d.ts +7 -0
  14. package/dist/execution-contracts.d.ts.map +1 -1
  15. package/dist/execution.js +1 -1
  16. package/dist/feedback-contracts.d.ts +32 -3
  17. package/dist/feedback-contracts.d.ts.map +1 -1
  18. package/dist/feedback.d.ts +28 -0
  19. package/dist/feedback.d.ts.map +1 -1
  20. package/dist/generation-retry.d.ts +45 -0
  21. package/dist/generation-retry.d.ts.map +1 -0
  22. package/dist/manifest.json +1 -1
  23. package/dist/models.js +1 -1
  24. package/dist/models.js.map +1 -1
  25. package/dist/proposal-contracts.d.ts +15 -0
  26. package/dist/proposal-contracts.d.ts.map +1 -1
  27. package/dist/proposal-dto.d.ts +6 -0
  28. package/dist/proposal-dto.d.ts.map +1 -1
  29. package/dist/proposals.d.ts +7 -0
  30. package/dist/proposals.d.ts.map +1 -1
  31. package/dist/proposals.js +61 -18
  32. package/dist/proposals.js.map +1 -1
  33. package/dist/review-dto.d.ts +56 -0
  34. package/dist/review-dto.d.ts.map +1 -1
  35. package/dist/review-host.d.ts +14 -1
  36. package/dist/review-host.d.ts.map +1 -1
  37. package/dist/server.d.ts +27 -1
  38. package/dist/server.d.ts.map +1 -1
  39. package/dist/server.js +127 -5
  40. package/dist/server.js.map +1 -1
  41. package/dist/smrt-knowledge.json +16 -16
  42. package/dist/svelte/argument-fields.d.ts +48 -0
  43. package/dist/svelte/argument-fields.d.ts.map +1 -0
  44. package/dist/svelte/argument-fields.js +96 -0
  45. package/dist/svelte/components/ActionReview.svelte +81 -11
  46. package/dist/svelte/components/ActionReview.svelte.d.ts +30 -1
  47. package/dist/svelte/components/ActionReview.svelte.d.ts.map +1 -1
  48. package/dist/svelte/components/ArgumentFields.svelte +176 -0
  49. package/dist/svelte/components/ArgumentFields.svelte.d.ts +27 -0
  50. package/dist/svelte/components/ArgumentFields.svelte.d.ts.map +1 -0
  51. package/dist/svelte/components/InboxView.svelte +137 -17
  52. package/dist/svelte/components/InboxView.svelte.d.ts +17 -0
  53. package/dist/svelte/components/InboxView.svelte.d.ts.map +1 -1
  54. package/dist/svelte/components/IntakeInbox.svelte +26 -2
  55. package/dist/svelte/components/IntakeInbox.svelte.d.ts +10 -1
  56. package/dist/svelte/components/IntakeInbox.svelte.d.ts.map +1 -1
  57. package/dist/svelte/components/IntakeWorkspace.svelte +23 -1
  58. package/dist/svelte/components/IntakeWorkspace.svelte.d.ts +13 -1
  59. package/dist/svelte/components/IntakeWorkspace.svelte.d.ts.map +1 -1
  60. package/dist/svelte/components/ReviewView.svelte +88 -10
  61. package/dist/svelte/components/ReviewView.svelte.d.ts.map +1 -1
  62. package/dist/svelte/components/TriageView.svelte +43 -3
  63. package/dist/svelte/components/TriageView.svelte.d.ts +10 -1
  64. package/dist/svelte/components/TriageView.svelte.d.ts.map +1 -1
  65. package/dist/svelte/i18n.d.ts +33 -0
  66. package/dist/svelte/i18n.d.ts.map +1 -1
  67. package/dist/svelte/i18n.js +36 -0
  68. package/dist/svelte/index.d.ts +2 -1
  69. package/dist/svelte/index.d.ts.map +1 -1
  70. package/dist/svelte/index.js +1 -0
  71. package/dist/svelte/upload-refusal.d.ts +25 -0
  72. package/dist/svelte/upload-refusal.d.ts.map +1 -0
  73. package/dist/svelte/upload-refusal.js +40 -0
  74. package/dist/svelte/view-utils.d.ts +8 -0
  75. package/dist/svelte/view-utils.d.ts.map +1 -1
  76. package/dist/svelte/view-utils.js +36 -0
  77. package/package.json +13 -13
  78. package/dist/chunks/feedback-DHcSKgVU.js.map +0 -1
  79. package/dist/chunks/proposal-contracts-CPH3ckgH.js.map +0 -1
@@ -1,5 +1,10 @@
1
1
  <script lang="ts">
2
- import { FilePicker, Input, Select } from '@happyvertical/smrt-ui/forms';
2
+ import {
3
+ Combobox,
4
+ FilePicker,
5
+ Input,
6
+ Select,
7
+ } from '@happyvertical/smrt-ui/forms';
3
8
  import { useI18n } from '@happyvertical/smrt-ui/i18n';
4
9
  import { Button } from '@happyvertical/smrt-ui/ui';
5
10
  import { onDestroy, onMount, type Snippet } from 'svelte';
@@ -9,7 +14,8 @@ import type {
9
14
  IntakeReviewHost,
10
15
  } from '../../review-dto.js';
11
16
  import { M } from '../i18n.js';
12
- import { evidenceUrl } from '../view-utils.js';
17
+ import { uploadRefusalMessage } from '../upload-refusal.js';
18
+ import { entryStatus, evidenceUrl } from '../view-utils.js';
13
19
  export interface Props {
14
20
  host: IntakeReviewHost;
15
21
  onselect?: (itemId: string) => void;
@@ -40,6 +46,19 @@ export interface Props {
40
46
  resolveInRow?: boolean;
41
47
  /** Numbers each page read when its request starts (`issued` on the page). */
42
48
  stamp?: () => number;
49
+ /**
50
+ * File types the picker offers (the input's `accept`, e.g.
51
+ * `application/pdf,image/*`); defaults to the host's `uploadAccept`. A hint
52
+ * only: the host's upload still enforces its own allowlist.
53
+ */
54
+ accept?: string;
55
+ /**
56
+ * Re-read the listed page on this interval (ms) while any displayed row is
57
+ * still processing, under the filter it was listed with. Off when absent
58
+ * (as before). Skipped while a read or operation is pending and while Load
59
+ * more has appended pages, which a re-read would drop.
60
+ */
61
+ listPollMs?: number;
43
62
  }
44
63
  let {
45
64
  host,
@@ -51,6 +70,8 @@ let {
51
70
  status,
52
71
  resolveInRow = true,
53
72
  stamp,
73
+ accept,
74
+ listPollMs,
54
75
  }: Props = $props();
55
76
  const { t } = useI18n();
56
77
  const states: InboxState[] = [
@@ -71,9 +92,13 @@ let items = $state<InboxEntry[]>([]),
71
92
  files = $state<File[]>([]),
72
93
  cursor = $state<string>(),
73
94
  pending = $state(false),
74
- error = $state(false);
95
+ error = $state(false),
96
+ // The host's reviewer-facing reason for refusing the last upload, if any.
97
+ refusal = $state<string>();
75
98
  let alive = true,
76
- epoch = 0;
99
+ epoch = 0,
100
+ // Load more appended a page to the displayed rows.
101
+ appended = false;
77
102
  let uploadRequestId: string | undefined;
78
103
  // The filter the displayed rows were listed under (the inputs may have been
79
104
  // edited since); `patch` keeps a row only while it still matches it.
@@ -121,6 +146,38 @@ export function patch(entry: InboxEntry, keep = false): InboxEntry[] {
121
146
  } else items = items.filter((row) => row.item.id !== entry.item.id);
122
147
  return $state.snapshot(items) as InboxEntry[];
123
148
  }
149
+ /**
150
+ * Re-read the first page, like Refresh (the filter as the controls show it).
151
+ * Resolves with the displayed rows and `shown`, `failed` (the list failed
152
+ * closed), or `pending` when a read or operation was in flight (nothing was
153
+ * read) or a newer read superseded this one.
154
+ */
155
+ export async function refresh(): Promise<{
156
+ items: InboxEntry[];
157
+ result: 'shown' | 'failed' | 'pending';
158
+ }> {
159
+ const read = pending ? 'superseded' : await load();
160
+ return {
161
+ items: $state.snapshot(items) as InboxEntry[],
162
+ result: read === 'superseded' ? 'pending' : read,
163
+ };
164
+ }
165
+ // Follow processing rows by re-reading the listed page (opt-in, #4504).
166
+ $effect(() => {
167
+ const every = listPollMs;
168
+ if (!every || !Number.isFinite(every) || every <= 0) return;
169
+ const timer = setInterval(() => {
170
+ if (
171
+ !alive ||
172
+ pending ||
173
+ appended ||
174
+ !items.some((entry) => entryStatus(entry).kind === 'pending')
175
+ )
176
+ return;
177
+ void load(false, true);
178
+ }, every);
179
+ return () => clearInterval(timer);
180
+ });
124
181
  /**
125
182
  * Append the next page, if any. Resolves with the displayed rows and what
126
183
  * happened: `appended` (or `none` when there is no further page), `pending`
@@ -153,7 +210,11 @@ onDestroy(() => {
153
210
  * failed (the rows were cleared), `superseded` when a newer read or the
154
211
  * component's end fenced it.
155
212
  */
156
- async function load(more = false): Promise<'shown' | 'failed' | 'superseded'> {
213
+ async function load(
214
+ more = false,
215
+ /** Re-read the first page under the listed filter (a poll), not the inputs. */
216
+ relist = false,
217
+ ): Promise<'shown' | 'failed' | 'superseded'> {
157
218
  const ticket = ++epoch;
158
219
  onreadstart?.();
159
220
  pending = true;
@@ -165,12 +226,13 @@ async function load(more = false): Promise<'shown' | 'failed' | 'superseded'> {
165
226
  // The displayed rows stay (controls disabled while pending) until the new
166
227
  // page arrives, so a workspace can keep patching and navigating them; a
167
228
  // failed read still clears them.
168
- const filter = more
169
- ? listed
170
- : {
171
- state: selectedState ? (selectedState as InboxState) : undefined,
172
- assigneeId: assignee || undefined,
173
- };
229
+ const filter =
230
+ more || relist
231
+ ? listed
232
+ : {
233
+ state: selectedState ? (selectedState as InboxState) : undefined,
234
+ assigneeId: assignee || undefined,
235
+ };
174
236
  try {
175
237
  const issued = stamp?.();
176
238
  const read = await host.list({ ...filter, cursor: next });
@@ -187,6 +249,7 @@ async function load(more = false): Promise<'shown' | 'failed' | 'superseded'> {
187
249
  };
188
250
  const kept = more ? { assignments, reasons } : undefined;
189
251
  items = more ? [...items, ...added] : read.items;
252
+ appended = more;
190
253
  cursor = read.nextCursor;
191
254
  assignments = Object.fromEntries(
192
255
  items.map((entry) => [
@@ -215,10 +278,15 @@ async function load(more = false): Promise<'shown' | 'failed' | 'superseded'> {
215
278
  }
216
279
  return page ? 'shown' : alive && ticket === epoch ? 'failed' : 'superseded';
217
280
  }
218
- async function mutate(operation: () => Promise<unknown>, selected = false) {
281
+ async function mutate(
282
+ operation: () => Promise<unknown>,
283
+ selected = false,
284
+ refused?: (error: unknown) => string | undefined,
285
+ ) {
219
286
  if (pending) return;
220
287
  pending = true;
221
288
  error = false;
289
+ refusal = undefined;
222
290
  const ticket = ++epoch;
223
291
  try {
224
292
  const result = await operation();
@@ -230,8 +298,17 @@ async function mutate(operation: () => Promise<unknown>, selected = false) {
230
298
  if (selected && result && typeof result === 'object' && 'itemId' in result)
231
299
  onselect?.(String(result.itemId));
232
300
  await load();
233
- } catch {
301
+ } catch (caught) {
234
302
  if (alive && ticket === epoch) {
303
+ // A host refusal with a reviewer-facing reason (a file type its
304
+ // allowlist refuses, say) is not an access result: the reason shows and
305
+ // the listed rows stay. Anything else fails closed.
306
+ const reason = refused?.(caught);
307
+ if (reason) {
308
+ refusal = reason;
309
+ pending = false;
310
+ return;
311
+ }
235
312
  items = [];
236
313
  assignments = {};
237
314
  error = true;
@@ -240,12 +317,54 @@ async function mutate(operation: () => Promise<unknown>, selected = false) {
240
317
  }
241
318
  }
242
319
  }
320
+ // Assignee picker (#4503): the host's search answers, newest query wins.
321
+ let assigneeOptions = $state<{ value: string; label: string }[]>([]),
322
+ assigneeLabels = $state<Record<string, string>>({}),
323
+ assigneeSearching = $state(false),
324
+ assigneeQueries = 0;
325
+ async function findAssignees(query: string) {
326
+ const search = host.assignees;
327
+ if (!search) return;
328
+ const ticket = ++assigneeQueries;
329
+ assigneeSearching = true;
330
+ try {
331
+ const found = await search(query);
332
+ if (!alive || ticket !== assigneeQueries) return;
333
+ const valid = (Array.isArray(found) ? found : [])
334
+ .filter(
335
+ (option) =>
336
+ typeof option?.id === 'string' &&
337
+ option.id &&
338
+ typeof option.label === 'string',
339
+ )
340
+ .slice(0, 50);
341
+ for (const option of valid) assigneeLabels[option.id] = option.label;
342
+ assigneeOptions = valid.map((option) => ({
343
+ value: option.id,
344
+ label: option.label,
345
+ }));
346
+ } catch {
347
+ // A failed search only offers nothing; it grants and reveals nothing.
348
+ if (alive && ticket === assigneeQueries) assigneeOptions = [];
349
+ } finally {
350
+ if (alive && ticket === assigneeQueries) assigneeSearching = false;
351
+ }
352
+ }
353
+ /** Picker options: clearing first (`''`), then the latest search answers. */
354
+ const pickerOptions = (empty: string) => [
355
+ { value: '', label: empty },
356
+ ...assigneeOptions,
357
+ ];
243
358
  async function upload() {
244
359
  if (pending || !files.length) return;
245
360
  uploadRequestId ??= crypto.randomUUID();
246
361
  const requestId = uploadRequestId;
247
362
  const originals = [...files];
248
- await mutate(() => host.upload(originals, requestId), true);
363
+ await mutate(
364
+ () => host.upload(originals, requestId),
365
+ true,
366
+ uploadRefusalMessage,
367
+ );
249
368
  }
250
369
  onMount(() => {
251
370
  void load();
@@ -255,13 +374,14 @@ onMount(() => {
255
374
  <h1>{t(M['ingestion.inbox'])}</h1>
256
375
  <div class="toolbar">
257
376
  <Select aria-label={t(M['ingestion.filter'])} bind:value={selectedState} disabled={pending} onchange={() => load()}><option value="">{t(M['ingestion.all'])}</option>{#each states as value}<option {value}>{t(M[`ingestion.${value}`])}</option>{/each}</Select>
258
- <Input aria-label={t(M['ingestion.assignee'])} bind:value={assignee} disabled={pending} />
377
+ {#if host.assignees}<Combobox label={t(M['ingestion.assigneeName'])} options={pickerOptions(t(M['ingestion.anyAssignee']))} bind:value={assignee} valueLabel={assigneeLabels[assignee] ?? assignee} filter={false} busy={assigneeSearching} status={assigneeSearching ? t(M['ingestion.searching']) : undefined} onquery={findAssignees} disabled={pending} />{:else}<Input aria-label={t(M['ingestion.assignee'])} bind:value={assignee} disabled={pending} />{/if}
259
378
  <Button onclick={() => load()} disabled={pending}>{t(M['ingestion.refresh'])}</Button>
260
379
  </div>
261
380
  <!-- Keep retry Files separately; an unchanged native FileList must not suppress a deliberate reselection. -->
262
- <FilePicker bind:files onclick={(event) => { event.currentTarget.value = ''; }} onchangefiles={() => { uploadRequestId = undefined; }} multiple label={t(M['ingestion.files'])} description={t(M['ingestion.filesHelp'])} disabled={pending} />
381
+ <FilePicker bind:files onclick={(event) => { event.currentTarget.value = ''; }} onchangefiles={() => { uploadRequestId = undefined; refusal = undefined; }} accept={accept ?? host.uploadAccept} multiple label={t(M['ingestion.files'])} description={t(M['ingestion.filesHelp'])} disabled={pending} />
263
382
  <Button disabled={pending || !files.length} onclick={upload}>{t(M['ingestion.upload'])}</Button>
264
383
  {#if error}<p role="alert">{t(M['ingestion.error'])}</p>{/if}
384
+ {#if refusal}<p role="alert" data-testid="upload-refused">{refusal}</p>{/if}
265
385
  {#if pending}<p role="status">{t(M['ingestion.loading'])}</p>{/if}
266
386
  {#if !pending && !error && !items.length}<p>{t(M['ingestion.empty'])}</p>{/if}
267
387
  <ul>{#each items as entry (entry.item.id)}<li data-item-id={entry.item.id} aria-current={entry.item.id === selectedId ? 'true' : undefined} class:selected={entry.item.id === selectedId}>
@@ -271,7 +391,7 @@ onMount(() => {
271
391
  {#if original}<a href={original} target="_blank" rel="noopener noreferrer" aria-label={`${t(M['ingestion.original'])}: ${entry.label}`}>{t(M['ingestion.original'])}</a>{/if}
272
392
  </div>{/if}{/if}
273
393
  <Button onclick={() => onselect?.(entry.item.id)} disabled={pending}>{t(M['ingestion.open'])}</Button>
274
- <Input aria-label={`${t(M['ingestion.assignee'])}: ${entry.label}`} bind:value={assignments[entry.item.id]} disabled={pending} />
394
+ {#if host.assignees}<Combobox label={`${t(M['ingestion.assigneeName'])}: ${entry.label}`} options={pickerOptions(t(M['ingestion.unassigned']))} bind:value={assignments[entry.item.id]} valueLabel={assigneeLabels[assignments[entry.item.id]] ?? assignments[entry.item.id]} filter={false} busy={assigneeSearching} status={assigneeSearching ? t(M['ingestion.searching']) : undefined} onquery={findAssignees} disabled={pending} />{:else}<Input aria-label={`${t(M['ingestion.assignee'])}: ${entry.label}`} bind:value={assignments[entry.item.id]} disabled={pending} />{/if}
275
395
  <Button disabled={pending} onclick={() => mutate(() => host.assign({itemId:entry.item.id,expectedVersion:entry.assignment.version,assigneeId:assignments[entry.item.id] || null,requestId:crypto.randomUUID()}))}>{t(M['ingestion.assign'])}</Button>
276
396
  {#if resolveInRow && host.resolve && !entry.item.disposition}
277
397
  <Input aria-label={`${t(M['ingestion.resolveReason'])}: ${entry.label}`} bind:value={reasons[entry.item.id]} disabled={pending} />
@@ -30,9 +30,26 @@ export interface Props {
30
30
  resolveInRow?: boolean;
31
31
  /** Numbers each page read when its request starts (`issued` on the page). */
32
32
  stamp?: () => number;
33
+ /**
34
+ * File types the picker offers (the input's `accept`, e.g.
35
+ * `application/pdf,image/*`); defaults to the host's `uploadAccept`. A hint
36
+ * only: the host's upload still enforces its own allowlist.
37
+ */
38
+ accept?: string;
39
+ /**
40
+ * Re-read the listed page on this interval (ms) while any displayed row is
41
+ * still processing, under the filter it was listed with. Off when absent
42
+ * (as before). Skipped while a read or operation is pending and while Load
43
+ * more has appended pages, which a re-read would drop.
44
+ */
45
+ listPollMs?: number;
33
46
  }
34
47
  declare const InboxView: import("svelte").Component<Props, {
35
48
  patch: (entry: InboxEntry, keep?: boolean) => InboxEntry[];
49
+ refresh: () => Promise<{
50
+ items: InboxEntry[];
51
+ result: "shown" | "failed" | "pending";
52
+ }>;
36
53
  more: () => Promise<{
37
54
  items: InboxEntry[];
38
55
  result: "appended" | "none" | "pending" | "failed";
@@ -1 +1 @@
1
- {"version":3,"file":"InboxView.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InboxView.svelte.ts"],"names":[],"mappings":"AAMA,OAAO,EAAsB,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,KAAK,EACV,UAAU,EAEV,gBAAgB,EACjB,MAAM,qBAAqB,CAAC;AAI7B,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IACjC,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CACR,KAAK,EAAE,UAAU,EAAE,EACnB,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE;QAAE,OAAO,EAAE,UAAU,EAAE,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,KACjE,IAAI,CAAC;IACV,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,sFAAsF;IACtF,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,MAAM,GAAG,SAAS,CAAC;IACnD,6FAA6F;IAC7F,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;CACtB;AA6PD,QAAA,MAAM,SAAS;mBA/LQ,UAAU,qBAAiB,UAAU,EAAE;gBAmCtC,OAAO,CAAC;QAC9B,KAAK,EAAE,UAAU,EAAE,CAAC;QACpB,MAAM,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;KACpD,CAAC;MAyJqD,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"InboxView.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/InboxView.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,EAAsB,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,KAAK,EACV,UAAU,EAEV,gBAAgB,EACjB,MAAM,qBAAqB,CAAC;AAK7B,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IACjC,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CACR,KAAK,EAAE,UAAU,EAAE,EACnB,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE;QAAE,OAAO,EAAE,UAAU,EAAE,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,KACjE,IAAI,CAAC;IACV,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,sFAAsF;IACtF,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,MAAM,GAAG,SAAS,CAAC;IACnD,6FAA6F;IAC7F,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;IACrB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAmWD,QAAA,MAAM,SAAS;mBA9RQ,UAAU,qBAAiB,UAAU,EAAE;mBAkCnC,OAAO,CAAC;QACjC,KAAK,EAAE,UAAU,EAAE,CAAC;QACpB,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;KACxC,CAAC;gBA8BsB,OAAO,CAAC;QAC9B,KAAK,EAAE,UAAU,EAAE,CAAC;QACpB,MAAM,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;KACpD,CAAC;MAwNqD,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -8,7 +8,31 @@ export interface Props {
8
8
  onselect?: (itemId: string) => void;
9
9
  /** Optional per-row aside (preview, original link); receives the page entry. */
10
10
  rowAside?: Snippet<[InboxEntry]>;
11
+ /** File types the upload picker offers; defaults to the host's `uploadAccept`. */
12
+ accept?: string;
13
+ /** Re-read the list on this interval (ms) while a listed row is still processing; off when absent. */
14
+ listPollMs?: number;
15
+ }
16
+ let {
17
+ host,
18
+ contextKey = '',
19
+ onselect,
20
+ rowAside,
21
+ accept,
22
+ listPollMs,
23
+ }: Props = $props();
24
+ let inbox = $state<ReturnType<typeof InboxView>>();
25
+ /**
26
+ * Re-read the list's first page, like its Refresh button; resolves `pending`
27
+ * without reading while a read or operation is in flight.
28
+ */
29
+ export function refresh(): Promise<{
30
+ items: InboxEntry[];
31
+ result: 'shown' | 'failed' | 'pending';
32
+ }> {
33
+ return inbox
34
+ ? inbox.refresh()
35
+ : Promise.resolve({ items: [], result: 'pending' as const });
11
36
  }
12
- let { host, contextKey = '', onselect, rowAside }: Props = $props();
13
37
  </script>
14
- {#key host}{#key contextKey}<InboxView {host} {onselect} {rowAside} />{/key}{/key}
38
+ {#key host}{#key contextKey}<InboxView bind:this={inbox} {host} {onselect} {rowAside} {accept} {listPollMs} />{/key}{/key}
@@ -6,8 +6,17 @@ export interface Props {
6
6
  onselect?: (itemId: string) => void;
7
7
  /** Optional per-row aside (preview, original link); receives the page entry. */
8
8
  rowAside?: Snippet<[InboxEntry]>;
9
+ /** File types the upload picker offers; defaults to the host's `uploadAccept`. */
10
+ accept?: string;
11
+ /** Re-read the list on this interval (ms) while a listed row is still processing; off when absent. */
12
+ listPollMs?: number;
9
13
  }
10
- declare const IntakeInbox: import("svelte").Component<Props, {}, "">;
14
+ declare const IntakeInbox: import("svelte").Component<Props, {
15
+ refresh: () => Promise<{
16
+ items: InboxEntry[];
17
+ result: "shown" | "failed" | "pending";
18
+ }>;
19
+ }, "">;
11
20
  type IntakeInbox = ReturnType<typeof IntakeInbox>;
12
21
  export default IntakeInbox;
13
22
  //# sourceMappingURL=IntakeInbox.svelte.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"IntakeInbox.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/IntakeInbox.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAGxE,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,gFAAgF;IAChF,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;CAClC;AAYD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"IntakeInbox.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/IntakeInbox.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAGxE,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,gFAAgF;IAChF,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IACjC,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sGAAsG;IACtG,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAgCD,QAAA,MAAM,WAAW;mBAbI,OAAO,CAAC;QAC3B,KAAK,EAAE,UAAU,EAAE,CAAC;QACpB,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;KACxC,CAAC;MAUuD,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -14,6 +14,13 @@ export interface Props {
14
14
  onselect?: (itemId: string | undefined) => void;
15
15
  /** Optional per-row aside (preview, original link); receives the page entry. */
16
16
  rowAside?: Snippet<[InboxEntry]>;
17
+ /** File types the upload picker offers; defaults to the host's `uploadAccept`. */
18
+ accept?: string;
19
+ /**
20
+ * Re-read the list on this interval (ms) while a listed row is still
21
+ * processing. Off when absent; processing rows are followed either way.
22
+ */
23
+ listPollMs?: number;
17
24
  }
18
25
  let {
19
26
  host,
@@ -22,6 +29,21 @@ let {
22
29
  pollMs = 2000,
23
30
  onselect,
24
31
  rowAside,
32
+ accept,
33
+ listPollMs,
25
34
  }: Props = $props();
35
+ let triage = $state<ReturnType<typeof TriageView>>();
36
+ /**
37
+ * Re-read the list's first page, like its Refresh button; resolves `pending`
38
+ * without reading while a read or operation is in flight.
39
+ */
40
+ export function refresh(): Promise<{
41
+ items: InboxEntry[];
42
+ result: 'shown' | 'failed' | 'pending';
43
+ }> {
44
+ return triage
45
+ ? triage.refresh()
46
+ : Promise.resolve({ items: [], result: 'pending' as const });
47
+ }
26
48
  </script>
27
- {#key host}{#key contextKey}<TriageView {host} {itemId} {pollMs} {onselect} {rowAside} />{/key}{/key}
49
+ {#key host}{#key contextKey}<TriageView bind:this={triage} {host} {itemId} {pollMs} {onselect} {rowAside} {accept} {listPollMs} />{/key}{/key}
@@ -12,8 +12,20 @@ export interface Props {
12
12
  onselect?: (itemId: string | undefined) => void;
13
13
  /** Optional per-row aside (preview, original link); receives the page entry. */
14
14
  rowAside?: Snippet<[InboxEntry]>;
15
+ /** File types the upload picker offers; defaults to the host's `uploadAccept`. */
16
+ accept?: string;
17
+ /**
18
+ * Re-read the list on this interval (ms) while a listed row is still
19
+ * processing. Off when absent; processing rows are followed either way.
20
+ */
21
+ listPollMs?: number;
15
22
  }
16
- declare const IntakeWorkspace: import("svelte").Component<Props, {}, "">;
23
+ declare const IntakeWorkspace: import("svelte").Component<Props, {
24
+ refresh: () => Promise<{
25
+ items: InboxEntry[];
26
+ result: "shown" | "failed" | "pending";
27
+ }>;
28
+ }, "">;
17
29
  type IntakeWorkspace = ReturnType<typeof IntakeWorkspace>;
18
30
  export default IntakeWorkspace;
19
31
  //# sourceMappingURL=IntakeWorkspace.svelte.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"IntakeWorkspace.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/IntakeWorkspace.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAGxE,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uFAAuF;IACvF,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IAChD,gFAAgF;IAChF,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;CAClC;AAmBD,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"IntakeWorkspace.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/IntakeWorkspace.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAGxE,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uFAAuF;IACvF,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IAChD,gFAAgF;IAChF,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IACjC,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAkCD,QAAA,MAAM,eAAe;mBAbA,OAAO,CAAC;QAC3B,KAAK,EAAE,UAAU,EAAE,CAAC;QACpB,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;KACxC,CAAC;MAU2D,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
@@ -9,7 +9,8 @@ import type {
9
9
  ProposalCatalogEntry,
10
10
  } from '../../proposal-dto.js';
11
11
  import type { IntakeReviewHost, ItemReviewView } from '../../review-dto.js';
12
- import { M } from '../i18n.js';
12
+ import { argumentFields } from '../argument-fields.js';
13
+ import { M, messageKey } from '../i18n.js';
13
14
  import {
14
15
  argumentsObject,
15
16
  draftArguments,
@@ -22,6 +23,7 @@ import {
22
23
  reviewInFlight,
23
24
  } from '../view-utils.js';
24
25
  import ActionReview, { type ReviewOutcome } from './ActionReview.svelte';
26
+ import ArgumentFields from './ArgumentFields.svelte';
25
27
  import CandidatePicker from './CandidatePicker.svelte';
26
28
  import EvidenceViewer from './EvidenceViewer.svelte';
27
29
  export interface Props {
@@ -434,6 +436,51 @@ async function run(operation: () => Promise<unknown>, outcome?: ReviewOutcome) {
434
436
  if (alive && ticket === epoch) deny();
435
437
  }
436
438
  }
439
+ /**
440
+ * A handler's display label (#4503) from the reviewer catalog, matching the
441
+ * version first; undefined when the handler declares none.
442
+ */
443
+ function handlerLabel(id: string | undefined, version: string | undefined) {
444
+ if (!id) return undefined;
445
+ const catalog = view?.catalog ?? [];
446
+ return (
447
+ catalog.find((entry) => entry.id === id && entry.version === version)
448
+ ?.label ?? catalog.find((entry) => entry.id === id && entry.label)?.label
449
+ );
450
+ }
451
+ /** The offered handler, else the reviewer catalog's entry: the argument fields' schema. */
452
+ function schemaFor(id: string | undefined, version: string | undefined) {
453
+ if (!id || !version) return undefined;
454
+ return (
455
+ view?.generation?.offered.find(
456
+ (entry) => entry.handler.id === id && entry.handler.version === version,
457
+ )?.handler ??
458
+ view?.catalog?.find((entry) => entry.id === id && entry.version === version)
459
+ );
460
+ }
461
+ function offeredCandidates(
462
+ id: string | undefined,
463
+ version: string | undefined,
464
+ ) {
465
+ return (
466
+ view?.generation?.offered.find(
467
+ (entry) => entry.handler.id === id && entry.handler.version === version,
468
+ )?.candidates ?? []
469
+ );
470
+ }
471
+ const evidenceOptions = $derived(
472
+ (view?.evidence ?? []).map((part) => ({
473
+ id: part.evidence.id,
474
+ label: part.label,
475
+ })),
476
+ );
477
+ const drawsFields = (entry: ProposalCatalogEntry | undefined) =>
478
+ !!entry && argumentFields(entry.argsSchema, entry.references).length > 0;
479
+ // Judgments sent from this view, by action revision; they outlive the reload
480
+ // that follows each one (#4503).
481
+ let judgments = $state<
482
+ Record<string, { judgment: 'correct' | 'incorrect'; id?: string }>
483
+ >({});
437
484
  function offeredHandler(suggestion: GeneratedSuggestion) {
438
485
  return view?.generation?.offered.find(
439
486
  (entry) =>
@@ -736,6 +783,35 @@ function instruct() {
736
783
  instruction = '';
737
784
  });
738
785
  }
786
+ /**
787
+ * "Try again" (#4504): the displayed interpretation failed (a provider error,
788
+ * or later work failed or never started) and nothing is in flight.
789
+ * Presentation only; the host rechecks the anchor and authority.
790
+ */
791
+ function retryable(current: ItemReviewView | undefined) {
792
+ return (
793
+ !!host.retry &&
794
+ current?.availability === 'available' &&
795
+ !current.entry.item.disposition &&
796
+ !inFlight(current) &&
797
+ !!instructionBinding(current) &&
798
+ (current.generation?.outcome === 'provider_error' ||
799
+ !!current.instruction?.failure)
800
+ );
801
+ }
802
+ function retry() {
803
+ const binding = instructionBinding(view);
804
+ const operation = host.retry;
805
+ if (!binding || !operation || !retryable(view)) return;
806
+ instructionBusy = false;
807
+ const input = { itemId, ...binding, requestId: crypto.randomUUID() };
808
+ // Like an instruction, a stale result reloads the view; never a denial.
809
+ void run(async () => {
810
+ const result = await operation(input);
811
+ if (result && 'stale' in result && result.stale === true)
812
+ instructionBusy = true;
813
+ });
814
+ }
739
815
  function locationLabel(location: EvidenceLocation) {
740
816
  switch (location.kind) {
741
817
  case 'page':
@@ -802,6 +878,7 @@ onMount(() => {
802
878
  {#if view.instruction.dropped}<p role="status" data-testid="dropped-instruction">{t(M['ingestion.droppedInstruction'])}: <q>{view.instruction.dropped.text}</q></p>{/if}
803
879
  {/if}
804
880
  {#if instructionBusy}<p role="status" data-testid="instruction-busy">{t(M['ingestion.instructionBusy'])}</p>{:else if unrecordedDraft !== undefined && unrecordedDraft === instruction}<p role="status" data-testid="instruction-not-recorded">{t(M['ingestion.instructionNotRecorded'])}</p>{/if}
881
+ {#if retryable(view)}<Button data-testid="retry-interpretation" disabled={pending} onclick={retry}>{t(M['ingestion.tryAgain'])}</Button>{/if}
805
882
  {#if host.instruct && !view.entry.item.disposition && instructionBinding(view)}
806
883
  <div><Textarea aria-label={t(M['ingestion.instructions'])} aria-describedby="instruction-length" maxlength={INSTRUCTION_MAX_LENGTH} bind:value={instruction} /><p id="instruction-length" data-testid="instruction-length">{t(M['ingestion.instructionLength'], { count: instruction.length, max: INSTRUCTION_MAX_LENGTH })}</p>{#if invalidDraft !== undefined && invalidDraft === instruction}<p role="alert" data-testid="instruction-invalid">{t(M['ingestion.instructionInvalid'], { max: INSTRUCTION_MAX_LENGTH })}</p>{/if}<Button disabled={!instructionValid || inFlight(view)} onclick={instruct}>{t(M['ingestion.reinterpret'])}</Button></div>
807
884
  {/if}
@@ -809,18 +886,19 @@ onMount(() => {
809
886
  <h2>{t(M['ingestion.extracted'])}</h2>
810
887
  {#each extracted as segment}
811
888
  <article><pre>{segment.text}</pre>
812
- {#if segment.confidence !== undefined}<p>{t(M['ingestion.confidence'])}: {segment.confidence}</p>{:else}<p>{t(M['ingestion.noConfidence'])}</p>{/if}
889
+ {#if segment.confidence !== undefined}<p>{t(M['ingestion.confidence'])}: {segment.confidence}</p>{/if}
813
890
  {#if segment.location}{@const url = locationUrl(segment.evidenceId,segment.location)}{#if url}<Button href={url} target="_blank" rel="noopener noreferrer">{locationLabel(segment.location)}</Button>{:else}<p>{locationLabel(segment.location)}</p>{/if}{/if}
814
891
  </article>
815
892
  {/each}
816
893
  {/if}
817
894
  {#if view.generation}
818
895
  {@const generation = view.generation}
819
- <h2>{t(M['ingestion.proposals'])}</h2><p>{generation.outcome}</p>
820
- {#if typeof view.analysis?.result.confidence === 'number' && Number.isFinite(view.analysis.result.confidence)}<p>{t(M['ingestion.confidence'])}: {view.analysis.result.confidence}</p>{:else}<p>{t(M['ingestion.noConfidence'])}</p>{/if}
896
+ {@const outcomeKey = messageKey('ingestion.outcome.', generation.outcome)}
897
+ <h2>{t(M['ingestion.proposals'])}</h2><p data-testid="generation-outcome">{outcomeKey ? t(M[outcomeKey]) : generation.outcome}</p>
898
+ {#if typeof view.analysis?.result.confidence === 'number' && Number.isFinite(view.analysis.result.confidence)}<p>{t(M['ingestion.confidence'])}: {view.analysis.result.confidence}</p>{/if}
821
899
  {#each generation.warnings as warning}<p>{warning}</p>{/each}
822
900
  {#each generation.suggestions as suggestion,index}
823
- <article><h3>{suggestion.handlerId}</h3><p>{suggestion.explanation}</p><pre>{JSON.stringify(suggestion.args,null,2)}</pre>
901
+ <article><h3>{handlerLabel(suggestion.handlerId, suggestion.handlerVersion) ?? suggestion.handlerId}</h3><p>{suggestion.explanation}</p><pre>{JSON.stringify(suggestion.args,null,2)}</pre>
824
902
  {#if suggestion.hostReview}<p data-testid="host-review">{t(M['ingestion.hostChecked'], { summary: [suggestion.hostReview.held ? t(M['ingestion.hostHeld']) : '', suggestion.hostReview.prefilled.length ? t(M['ingestion.hostFilled'], { fields: suggestion.hostReview.prefilled.join(', ') }) : '', suggestion.hostReview.reasons.length ? t(M['ingestion.hostReasons'], { reasons: suggestion.hostReview.reasons.join(', ') }) : ''].filter(Boolean).join('; ') })}</p>{/if}
825
903
  {#if suggestion.alternatives.length}<h4>{t(M['ingestion.alternatives'])}</h4><ul>{#each suggestion.alternatives as alternative}<li>{alternative}</li>{/each}</ul>{/if}
826
904
  {#if suggestion.missingFields.length}<h4>{t(M['ingestion.missing'])}</h4><ul>{#each suggestion.missingFields as missing}<li>{missing}</li>{/each}</ul>{/if}
@@ -830,8 +908,8 @@ onMount(() => {
830
908
  {@const handler = offeredHandler(suggestion)}
831
909
  {@const key = draftKey(index)}
832
910
  <p>{t(M['ingestion.completeSuggestion'])}</p>
833
- <Textarea aria-label={t(M['ingestion.suggestionArgs'])} bind:value={drafts[key]} rows={6} />
834
- {#if handler}<CandidatePicker {host} {itemId} catalog={handler} {deny} onchoose={chooseFor(index)} />{/if}
911
+ <ArgumentFields bind:value={drafts[key]} label={t(M['ingestion.suggestionArgs'])} catalog={handler} candidates={offeredCandidates(suggestion.handlerId, suggestion.handlerVersion)} evidence={evidenceOptions} rows={6} {host} {itemId} {deny} />
912
+ {#if handler && !drawsFields(handler)}<CandidatePicker {host} {itemId} catalog={handler} {deny} onchoose={chooseFor(index)} />{/if}
835
913
  {#if edited(index)}<p>{t(M['ingestion.reviewAgain'])}</p>{/if}
836
914
  {#if invalidDrafts[key] && !parseable(drafts[key])}<p role="alert">{t(M['ingestion.invalid'])}</p>{/if}
837
915
  {/if}
@@ -852,10 +930,10 @@ onMount(() => {
852
930
  {:else}
853
931
  {#if host.startAction && view.catalog?.some((entry) => entry.kind === 'operation')}
854
932
  <fieldset class="start"><legend>{t(M['ingestion.startAs'])}</legend>
855
- <Select aria-label={t(M['ingestion.startHandler'])} bind:value={startHandler} onchange={draftStart} disabled={pending}><option value="">{t(M['ingestion.startHandler'])}</option>{#each view.catalog as entry (`${entry.id}@${entry.version}`)}{#if entry.kind === 'operation'}<option value={`${entry.id}@${entry.version}`}>{entry.id}</option>{/if}{/each}</Select>
933
+ <Select aria-label={t(M['ingestion.startHandler'])} bind:value={startHandler} onchange={draftStart} disabled={pending}><option value="">{t(M['ingestion.startHandler'])}</option>{#each view.catalog as entry (`${entry.id}@${entry.version}`)}{#if entry.kind === 'operation'}<option value={`${entry.id}@${entry.version}`}>{entry.label ?? entry.id}</option>{/if}{/each}</Select>
856
934
  {#if startEntry}
857
935
  <p>{startEntry.description}</p>
858
- <Textarea aria-label={t(M['ingestion.startArgs'])} bind:value={startArgs} rows={8} />
936
+ <ArgumentFields bind:value={startArgs} label={t(M['ingestion.startArgs'])} catalog={startEntry} candidates={offeredCandidates(startEntry.id, startEntry.version)} evidence={evidenceOptions} {host} {itemId} {deny} />
859
937
  {#if startMissing.length}<h4>{t(M['ingestion.missing'])}</h4><ul>{#each startMissing as missing}<li>{missing}</li>{/each}</ul>{/if}
860
938
  <Input aria-label={t(M['ingestion.reason'])} bind:value={startReason} />
861
939
  <p>{t(M['ingestion.startHelp'])}</p>
@@ -874,7 +952,7 @@ onMount(() => {
874
952
  {/if}
875
953
  {#if !view.reviews.actions.length}<p>{t(M['ingestion.noReviews'])}</p>{/if}
876
954
  {#each view.reviews.actions as action (`${action.review.actionId}:${action.review.revision}:${action.review.reviewVersion}`)}
877
- <ActionReview {host} {itemId} {action} {run} {deny} ondirty={(edited) => { actionDirty[action.review.actionId] = edited; }} resolved={!!view.entry.item.disposition} currentAttemptId={action.stalePlan && view.reviews.actions.find((entry) => entry.stalePlan?.id === action.stalePlan?.id) === action ? action.attemptId : undefined} catalog={view.generation?.offered.find((entry) => entry.handler.id === action.handlerId && entry.handler.version === action.handlerVersion)?.handler} />
955
+ <ActionReview {host} {itemId} {action} {run} {deny} label={handlerLabel(action.handlerId, action.handlerVersion)} fields={schemaFor(action.handlerId, action.handlerVersion)} candidates={offeredCandidates(action.handlerId, action.handlerVersion)} evidence={evidenceOptions} sent={judgments[`${action.review.actionId}:${action.review.revision}`]} onjudged={(judged) => { judgments[`${action.review.actionId}:${action.review.revision}`] = judged; }} ondirty={(edited) => { actionDirty[action.review.actionId] = edited; }} resolved={!!view.entry.item.disposition} currentAttemptId={action.stalePlan && view.reviews.actions.find((entry) => entry.stalePlan?.id === action.stalePlan?.id) === action ? action.attemptId : undefined} catalog={view.generation?.offered.find((entry) => entry.handler.id === action.handlerId && entry.handler.version === action.handlerVersion)?.handler} />
878
956
  {/each}
879
957
  {#if view.reviews.nextCursor}<Button onclick={() => load(loadedPages + 1)}>{t(M['ingestion.more'])}</Button>{/if}
880
958
  </div></div>
@@ -1 +1 @@
1
- {"version":3,"file":"ReviewView.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ReviewView.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAa5E,OAAqB,EAAE,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAIzE,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8FAA8F;IAC9F,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,CACP,IAAI,EAAE,cAAc,GAAG,SAAS,EAChC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,KACX,IAAI,CAAC;IACV,qFAAqF;IACrF,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;IACrB,0DAA0D;IAC1D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,mFAAmF;IACnF,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;CACpE;AAi1BD,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"ReviewView.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/ReviewView.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAc5E,OAAqB,EAAE,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAKzE,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8FAA8F;IAC9F,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,CACP,IAAI,EAAE,cAAc,GAAG,SAAS,EAChC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,KACX,IAAI,CAAC;IACV,qFAAqF;IACrF,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;IACrB,0DAA0D;IAC1D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,mFAAmF;IACnF,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;CACpE;AA+5BD,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}