@joewinke/jatui 0.37.0 → 0.38.0
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/dist/artifact-blocks/AttachmentChip.svelte +66 -0
- package/dist/artifact-blocks/AttachmentChip.svelte.d.ts +7 -0
- package/dist/artifact-blocks/BlockComments.svelte +309 -0
- package/dist/artifact-blocks/BlockComments.svelte.d.ts +33 -0
- package/dist/artifact-blocks/BlockRenderer.svelte +674 -0
- package/dist/artifact-blocks/BlockRenderer.svelte.d.ts +55 -0
- package/dist/artifact-blocks/BlockVerdict.svelte +151 -0
- package/dist/artifact-blocks/BlockVerdict.svelte.d.ts +13 -0
- package/dist/artifact-blocks/BlocksArtifactView.svelte +1616 -0
- package/dist/artifact-blocks/BlocksArtifactView.svelte.d.ts +113 -0
- package/dist/artifact-blocks/CONTRACT.md +225 -0
- package/dist/artifact-blocks/CommentPill.svelte +49 -0
- package/dist/artifact-blocks/CommentPill.svelte.d.ts +16 -0
- package/dist/artifact-blocks/FoldedRounds.svelte +95 -0
- package/dist/artifact-blocks/FoldedRounds.svelte.d.ts +9 -0
- package/dist/artifact-blocks/Glyph.svelte +54 -0
- package/dist/artifact-blocks/Glyph.svelte.d.ts +10 -0
- package/dist/artifact-blocks/HistoryScrubber.svelte +500 -0
- package/dist/artifact-blocks/HistoryScrubber.svelte.d.ts +15 -0
- package/dist/artifact-blocks/Markdown.svelte +186 -0
- package/dist/artifact-blocks/Markdown.svelte.d.ts +30 -0
- package/dist/artifact-blocks/NoteComposer.svelte +31 -0
- package/dist/artifact-blocks/NoteComposer.svelte.d.ts +8 -0
- package/dist/artifact-blocks/PlainComposer.svelte +200 -0
- package/dist/artifact-blocks/PlainComposer.svelte.d.ts +15 -0
- package/dist/artifact-blocks/SectionTools.svelte +102 -0
- package/dist/artifact-blocks/SectionTools.svelte.d.ts +19 -0
- package/dist/artifact-blocks/adapter.d.ts +13 -0
- package/dist/artifact-blocks/adapter.js +31 -0
- package/dist/artifact-blocks/autoSend.d.ts +49 -0
- package/dist/artifact-blocks/autoSend.js +103 -0
- package/dist/artifact-blocks/blocks/actions/ActionsBlock.svelte +557 -0
- package/dist/artifact-blocks/blocks/actions/ActionsBlock.svelte.d.ts +48 -0
- package/dist/artifact-blocks/blocks/actions/block.d.ts +8 -0
- package/dist/artifact-blocks/blocks/actions/block.js +74 -0
- package/dist/artifact-blocks/blocks/actions/requests.d.ts +62 -0
- package/dist/artifact-blocks/blocks/actions/requests.js +26 -0
- package/dist/artifact-blocks/blocks/actions/schema.d.ts +168 -0
- package/dist/artifact-blocks/blocks/actions/schema.js +253 -0
- package/dist/artifact-blocks/blocks/callout/CalloutBlock.svelte +779 -0
- package/dist/artifact-blocks/blocks/callout/CalloutBlock.svelte.d.ts +40 -0
- package/dist/artifact-blocks/blocks/callout/block.d.ts +7 -0
- package/dist/artifact-blocks/blocks/callout/block.js +39 -0
- package/dist/artifact-blocks/blocks/callout/schema.d.ts +72 -0
- package/dist/artifact-blocks/blocks/callout/schema.js +115 -0
- package/dist/artifact-blocks/blocks/chart/ChartBlock.svelte +875 -0
- package/dist/artifact-blocks/blocks/chart/ChartBlock.svelte.d.ts +59 -0
- package/dist/artifact-blocks/blocks/chart/block.d.ts +9 -0
- package/dist/artifact-blocks/blocks/chart/block.js +70 -0
- package/dist/artifact-blocks/blocks/chart/schema.d.ts +171 -0
- package/dist/artifact-blocks/blocks/chart/schema.js +498 -0
- package/dist/artifact-blocks/blocks/chart/thumb.d.ts +2 -0
- package/dist/artifact-blocks/blocks/chart/thumb.js +201 -0
- package/dist/artifact-blocks/blocks/checklist/ChecklistBlock.svelte +1048 -0
- package/dist/artifact-blocks/blocks/checklist/ChecklistBlock.svelte.d.ts +16 -0
- package/dist/artifact-blocks/blocks/checklist/Icon.svelte +105 -0
- package/dist/artifact-blocks/blocks/checklist/Icon.svelte.d.ts +17 -0
- package/dist/artifact-blocks/blocks/checklist/block.d.ts +10 -0
- package/dist/artifact-blocks/blocks/checklist/block.js +63 -0
- package/dist/artifact-blocks/blocks/checklist/itemReview.d.ts +16 -0
- package/dist/artifact-blocks/blocks/checklist/itemReview.js +63 -0
- package/dist/artifact-blocks/blocks/checklist/labels.d.ts +62 -0
- package/dist/artifact-blocks/blocks/checklist/labels.js +96 -0
- package/dist/artifact-blocks/blocks/checklist/retest.d.ts +43 -0
- package/dist/artifact-blocks/blocks/checklist/retest.js +68 -0
- package/dist/artifact-blocks/blocks/checklist/schema.d.ts +84 -0
- package/dist/artifact-blocks/blocks/checklist/schema.js +91 -0
- package/dist/artifact-blocks/blocks/code/CodeBlock.svelte +128 -0
- package/dist/artifact-blocks/blocks/code/CodeBlock.svelte.d.ts +12 -0
- package/dist/artifact-blocks/blocks/code/block.d.ts +8 -0
- package/dist/artifact-blocks/blocks/code/block.js +31 -0
- package/dist/artifact-blocks/blocks/code/highlight.d.ts +34 -0
- package/dist/artifact-blocks/blocks/code/highlight.js +280 -0
- package/dist/artifact-blocks/blocks/code/schema.d.ts +32 -0
- package/dist/artifact-blocks/blocks/code/schema.js +39 -0
- package/dist/artifact-blocks/blocks/collage/CollageBlock.svelte +290 -0
- package/dist/artifact-blocks/blocks/collage/CollageBlock.svelte.d.ts +29 -0
- package/dist/artifact-blocks/blocks/collage/block.d.ts +9 -0
- package/dist/artifact-blocks/blocks/collage/block.js +29 -0
- package/dist/artifact-blocks/blocks/collage/schema.d.ts +52 -0
- package/dist/artifact-blocks/blocks/collage/schema.js +86 -0
- package/dist/artifact-blocks/blocks/compare/CompareBlock.svelte +484 -0
- package/dist/artifact-blocks/blocks/compare/CompareBlock.svelte.d.ts +25 -0
- package/dist/artifact-blocks/blocks/compare/block.d.ts +9 -0
- package/dist/artifact-blocks/blocks/compare/block.js +32 -0
- package/dist/artifact-blocks/blocks/compare/schema.d.ts +74 -0
- package/dist/artifact-blocks/blocks/compare/schema.js +148 -0
- package/dist/artifact-blocks/blocks/diagram/DiagramBlock.svelte +392 -0
- package/dist/artifact-blocks/blocks/diagram/DiagramBlock.svelte.d.ts +30 -0
- package/dist/artifact-blocks/blocks/diagram/block.d.ts +9 -0
- package/dist/artifact-blocks/blocks/diagram/block.js +50 -0
- package/dist/artifact-blocks/blocks/diagram/constants.d.ts +48 -0
- package/dist/artifact-blocks/blocks/diagram/constants.js +53 -0
- package/dist/artifact-blocks/blocks/diagram/schema.d.ts +161 -0
- package/dist/artifact-blocks/blocks/diagram/schema.js +621 -0
- package/dist/artifact-blocks/blocks/diagram/thumb.d.ts +2 -0
- package/dist/artifact-blocks/blocks/diagram/thumb.js +157 -0
- package/dist/artifact-blocks/blocks/diagram/types.d.ts +74 -0
- package/dist/artifact-blocks/blocks/diagram/types.js +1 -0
- package/dist/artifact-blocks/blocks/diff/DiffBlock.svelte +81 -0
- package/dist/artifact-blocks/blocks/diff/DiffBlock.svelte.d.ts +10 -0
- package/dist/artifact-blocks/blocks/diff/block.d.ts +7 -0
- package/dist/artifact-blocks/blocks/diff/block.js +36 -0
- package/dist/artifact-blocks/blocks/diff/schema.d.ts +18 -0
- package/dist/artifact-blocks/blocks/diff/schema.js +35 -0
- package/dist/artifact-blocks/blocks/diff-review/DiffReviewBlock.svelte +1055 -0
- package/dist/artifact-blocks/blocks/diff-review/DiffReviewBlock.svelte.d.ts +5 -0
- package/dist/artifact-blocks/blocks/diff-review/block.d.ts +15 -0
- package/dist/artifact-blocks/blocks/diff-review/block.js +64 -0
- package/dist/artifact-blocks/blocks/diff-review/decisions.d.ts +57 -0
- package/dist/artifact-blocks/blocks/diff-review/decisions.js +80 -0
- package/dist/artifact-blocks/blocks/diff-review/schema.d.ts +13 -0
- package/dist/artifact-blocks/blocks/diff-review/schema.js +84 -0
- package/dist/artifact-blocks/blocks/diff-review/types.d.ts +35 -0
- package/dist/artifact-blocks/blocks/diff-review/types.js +1 -0
- package/dist/artifact-blocks/blocks/explainer/ExplainerBlock.svelte +145 -0
- package/dist/artifact-blocks/blocks/explainer/ExplainerBlock.svelte.d.ts +5 -0
- package/dist/artifact-blocks/blocks/explainer/block.d.ts +8 -0
- package/dist/artifact-blocks/blocks/explainer/block.js +21 -0
- package/dist/artifact-blocks/blocks/explainer/schema.d.ts +26 -0
- package/dist/artifact-blocks/blocks/explainer/schema.js +69 -0
- package/dist/artifact-blocks/blocks/group-content.d.ts +10 -0
- package/dist/artifact-blocks/blocks/group-content.js +16 -0
- package/dist/artifact-blocks/blocks/group-data.d.ts +10 -0
- package/dist/artifact-blocks/blocks/group-data.js +7 -0
- package/dist/artifact-blocks/blocks/group-review.d.ts +10 -0
- package/dist/artifact-blocks/blocks/group-review.js +12 -0
- package/dist/artifact-blocks/blocks/group-visual.d.ts +17 -0
- package/dist/artifact-blocks/blocks/group-visual.js +14 -0
- package/dist/artifact-blocks/blocks/map/MapBlock.svelte +413 -0
- package/dist/artifact-blocks/blocks/map/MapBlock.svelte.d.ts +53 -0
- package/dist/artifact-blocks/blocks/map/block.d.ts +9 -0
- package/dist/artifact-blocks/blocks/map/block.js +62 -0
- package/dist/artifact-blocks/blocks/map/schema.d.ts +123 -0
- package/dist/artifact-blocks/blocks/map/schema.js +113 -0
- package/dist/artifact-blocks/blocks/map/thumb.d.ts +2 -0
- package/dist/artifact-blocks/blocks/map/thumb.js +69 -0
- package/dist/artifact-blocks/blocks/markdown/MarkdownBlock.svelte +33 -0
- package/dist/artifact-blocks/blocks/markdown/MarkdownBlock.svelte.d.ts +15 -0
- package/dist/artifact-blocks/blocks/markdown/block.d.ts +7 -0
- package/dist/artifact-blocks/blocks/markdown/block.js +28 -0
- package/dist/artifact-blocks/blocks/markdown/render.d.ts +17 -0
- package/dist/artifact-blocks/blocks/markdown/render.js +115 -0
- package/dist/artifact-blocks/blocks/markdown/schema.d.ts +10 -0
- package/dist/artifact-blocks/blocks/markdown/schema.js +23 -0
- package/dist/artifact-blocks/blocks/media-picker/MediaPickerBlock.svelte +2112 -0
- package/dist/artifact-blocks/blocks/media-picker/MediaPickerBlock.svelte.d.ts +5 -0
- package/dist/artifact-blocks/blocks/media-picker/block.d.ts +24 -0
- package/dist/artifact-blocks/blocks/media-picker/block.js +137 -0
- package/dist/artifact-blocks/blocks/media-picker/choices.d.ts +28 -0
- package/dist/artifact-blocks/blocks/media-picker/choices.js +35 -0
- package/dist/artifact-blocks/blocks/media-picker/decisions.d.ts +51 -0
- package/dist/artifact-blocks/blocks/media-picker/decisions.js +75 -0
- package/dist/artifact-blocks/blocks/media-picker/schema.d.ts +7 -0
- package/dist/artifact-blocks/blocks/media-picker/schema.js +199 -0
- package/dist/artifact-blocks/blocks/media-picker/types.d.ts +92 -0
- package/dist/artifact-blocks/blocks/media-picker/types.js +1 -0
- package/dist/artifact-blocks/blocks/media-picker/versions.d.ts +51 -0
- package/dist/artifact-blocks/blocks/media-picker/versions.js +118 -0
- package/dist/artifact-blocks/blocks/mockup/MockupBlock.svelte +213 -0
- package/dist/artifact-blocks/blocks/mockup/MockupBlock.svelte.d.ts +5 -0
- package/dist/artifact-blocks/blocks/mockup/block.d.ts +26 -0
- package/dist/artifact-blocks/blocks/mockup/block.js +52 -0
- package/dist/artifact-blocks/blocks/mockup/doc.d.ts +24 -0
- package/dist/artifact-blocks/blocks/mockup/doc.js +44 -0
- package/dist/artifact-blocks/blocks/mockup/sandbox.d.ts +21 -0
- package/dist/artifact-blocks/blocks/mockup/sandbox.js +27 -0
- package/dist/artifact-blocks/blocks/mockup/schema.d.ts +7 -0
- package/dist/artifact-blocks/blocks/mockup/schema.js +90 -0
- package/dist/artifact-blocks/blocks/mockup/types.d.ts +36 -0
- package/dist/artifact-blocks/blocks/mockup/types.js +17 -0
- package/dist/artifact-blocks/blocks/program/ProgramBlock.svelte +359 -0
- package/dist/artifact-blocks/blocks/program/ProgramBlock.svelte.d.ts +32 -0
- package/dist/artifact-blocks/blocks/program/block.d.ts +35 -0
- package/dist/artifact-blocks/blocks/program/block.js +68 -0
- package/dist/artifact-blocks/blocks/program/schema.d.ts +132 -0
- package/dist/artifact-blocks/blocks/program/schema.js +268 -0
- package/dist/artifact-blocks/blocks/stats/StatsBlock.svelte +315 -0
- package/dist/artifact-blocks/blocks/stats/StatsBlock.svelte.d.ts +24 -0
- package/dist/artifact-blocks/blocks/stats/block.d.ts +7 -0
- package/dist/artifact-blocks/blocks/stats/block.js +49 -0
- package/dist/artifact-blocks/blocks/stats/schema.d.ts +82 -0
- package/dist/artifact-blocks/blocks/stats/schema.js +43 -0
- package/dist/artifact-blocks/blocks/stats/thumb.d.ts +2 -0
- package/dist/artifact-blocks/blocks/stats/thumb.js +102 -0
- package/dist/artifact-blocks/blocks/table/TableBlock.svelte +490 -0
- package/dist/artifact-blocks/blocks/table/TableBlock.svelte.d.ts +28 -0
- package/dist/artifact-blocks/blocks/table/block.d.ts +9 -0
- package/dist/artifact-blocks/blocks/table/block.js +100 -0
- package/dist/artifact-blocks/blocks/table/schema.d.ts +143 -0
- package/dist/artifact-blocks/blocks/table/schema.js +278 -0
- package/dist/artifact-blocks/blocks/timeline/TimelineBlock.svelte +347 -0
- package/dist/artifact-blocks/blocks/timeline/TimelineBlock.svelte.d.ts +42 -0
- package/dist/artifact-blocks/blocks/timeline/block.d.ts +9 -0
- package/dist/artifact-blocks/blocks/timeline/block.js +96 -0
- package/dist/artifact-blocks/blocks/timeline/constants.d.ts +26 -0
- package/dist/artifact-blocks/blocks/timeline/constants.js +27 -0
- package/dist/artifact-blocks/blocks/timeline/schema.d.ts +102 -0
- package/dist/artifact-blocks/blocks/timeline/schema.js +405 -0
- package/dist/artifact-blocks/blocks/timeline/thumb.d.ts +2 -0
- package/dist/artifact-blocks/blocks/timeline/thumb.js +117 -0
- package/dist/artifact-blocks/blocks/timeline/types.d.ts +66 -0
- package/dist/artifact-blocks/blocks/timeline/types.js +1 -0
- package/dist/artifact-blocks/blocks/transcript/TranscriptBlock.svelte +169 -0
- package/dist/artifact-blocks/blocks/transcript/TranscriptBlock.svelte.d.ts +26 -0
- package/dist/artifact-blocks/blocks/transcript/block.d.ts +9 -0
- package/dist/artifact-blocks/blocks/transcript/block.js +86 -0
- package/dist/artifact-blocks/blocks/transcript/schema.d.ts +114 -0
- package/dist/artifact-blocks/blocks/transcript/schema.js +126 -0
- package/dist/artifact-blocks/builtins.d.ts +40 -0
- package/dist/artifact-blocks/builtins.js +22 -0
- package/dist/artifact-blocks/contractBlocks.d.ts +92 -0
- package/dist/artifact-blocks/contractBlocks.js +279 -0
- package/dist/artifact-blocks/history.d.ts +140 -0
- package/dist/artifact-blocks/history.js +363 -0
- package/dist/artifact-blocks/index.d.ts +40 -0
- package/dist/artifact-blocks/index.js +46 -0
- package/dist/artifact-blocks/listen/context.d.ts +30 -0
- package/dist/artifact-blocks/listen/context.js +30 -0
- package/dist/artifact-blocks/liveData.d.ts +78 -0
- package/dist/artifact-blocks/liveData.js +137 -0
- package/dist/artifact-blocks/liveDataPoller.d.ts +41 -0
- package/dist/artifact-blocks/liveDataPoller.js +140 -0
- package/dist/artifact-blocks/openNav.d.ts +46 -0
- package/dist/artifact-blocks/openNav.js +106 -0
- package/dist/artifact-blocks/pageReviewKeys.d.ts +17 -0
- package/dist/artifact-blocks/pageReviewKeys.js +115 -0
- package/dist/artifact-blocks/params.d.ts +98 -0
- package/dist/artifact-blocks/params.js +159 -0
- package/dist/artifact-blocks/registry.d.ts +27 -0
- package/dist/artifact-blocks/registry.js +83 -0
- package/dist/artifact-blocks/review/BlockIcon.svelte +99 -0
- package/dist/artifact-blocks/review/BlockIcon.svelte.d.ts +17 -0
- package/dist/artifact-blocks/review/BulkRejectForm.svelte +94 -0
- package/dist/artifact-blocks/review/BulkRejectForm.svelte.d.ts +11 -0
- package/dist/artifact-blocks/review/BulkUndoBar.svelte +40 -0
- package/dist/artifact-blocks/review/BulkUndoBar.svelte.d.ts +9 -0
- package/dist/artifact-blocks/review/ItemNote.svelte +193 -0
- package/dist/artifact-blocks/review/ItemNote.svelte.d.ts +33 -0
- package/dist/artifact-blocks/review/NoteToggle.svelte +52 -0
- package/dist/artifact-blocks/review/NoteToggle.svelte.d.ts +12 -0
- package/dist/artifact-blocks/review/TeamChip.svelte +71 -0
- package/dist/artifact-blocks/review/TeamChip.svelte.d.ts +8 -0
- package/dist/artifact-blocks/review/attachments.d.ts +10 -0
- package/dist/artifact-blocks/review/attachments.js +37 -0
- package/dist/artifact-blocks/review/bulk.d.ts +52 -0
- package/dist/artifact-blocks/review/bulk.js +61 -0
- package/dist/artifact-blocks/review/itemReview.d.ts +33 -0
- package/dist/artifact-blocks/review/itemReview.js +112 -0
- package/dist/artifact-blocks/review/roundLines.d.ts +14 -0
- package/dist/artifact-blocks/review/roundLines.js +20 -0
- package/dist/artifact-blocks/review/rounds.d.ts +86 -0
- package/dist/artifact-blocks/review/rounds.js +302 -0
- package/dist/artifact-blocks/review/saver.d.ts +23 -0
- package/dist/artifact-blocks/review/saver.js +48 -0
- package/dist/artifact-blocks/review/team.d.ts +31 -0
- package/dist/artifact-blocks/review/team.js +42 -0
- package/dist/artifact-blocks/review/visualOrder.d.ts +29 -0
- package/dist/artifact-blocks/review/visualOrder.js +66 -0
- package/dist/artifact-blocks/review.d.ts +180 -0
- package/dist/artifact-blocks/review.js +716 -0
- package/dist/artifact-blocks/thumb-kit.d.ts +80 -0
- package/dist/artifact-blocks/thumb-kit.js +149 -0
- package/dist/artifact-blocks/types.d.ts +513 -0
- package/dist/artifact-blocks/types.js +1 -0
- package/package.json +11 -2
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { VerdictOption } from "../../actions/reviewKeys"
|
|
3
|
+
|
|
4
|
+
/** The per-item note's ReviewNote props (MOS ReviewComposer note mode). */
|
|
5
|
+
export interface ItemNoteProps {
|
|
6
|
+
value?: string
|
|
7
|
+
verdict?: string | null
|
|
8
|
+
options?: readonly VerdictOption[]
|
|
9
|
+
round?: string | number | null
|
|
10
|
+
saveFailed?: boolean
|
|
11
|
+
onSave?: (
|
|
12
|
+
text: string,
|
|
13
|
+
ctx: { round: string | number | null },
|
|
14
|
+
) => void | Promise<void>
|
|
15
|
+
onInput?: (text: string) => void
|
|
16
|
+
debounceMs?: number
|
|
17
|
+
returnFocusTo?: () => HTMLElement | null | undefined
|
|
18
|
+
id?: string
|
|
19
|
+
label?: string
|
|
20
|
+
placeholder?: string
|
|
21
|
+
disabled?: boolean
|
|
22
|
+
}
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<script lang="ts">
|
|
26
|
+
/**
|
|
27
|
+
* ItemNote — the per-item verdict note of the item-review blocks
|
|
28
|
+
* (media-picker, diff-review). Ported from MOS ReviewComposer's `note`
|
|
29
|
+
* mode: jatui ReviewNote IS the text leg (its round model,
|
|
30
|
+
* open-on-negative-verdict and debounced save are unchanged), and an
|
|
31
|
+
* attach lane rides under it while the note is open.
|
|
32
|
+
*
|
|
33
|
+
* The upload leg is the HOST's (`HostAdapter.upload(artifactId)`): it
|
|
34
|
+
* stores the bytes first and returns the descriptor, which this hands to
|
|
35
|
+
* `onClipsChange` for the block to save on the item's entry. No upload
|
|
36
|
+
* leg → text-only, exactly like the plain composer. Recording (voice /
|
|
37
|
+
* video / screen) is the host composer's job, not this lane's.
|
|
38
|
+
*/
|
|
39
|
+
import ReviewNote from "../../components/review/ReviewNote.svelte"
|
|
40
|
+
import AttachmentChip from "../AttachmentChip.svelte"
|
|
41
|
+
import { hostAdapter } from "../adapter.js"
|
|
42
|
+
import type { NoteAttachment } from "../types.js"
|
|
43
|
+
|
|
44
|
+
let {
|
|
45
|
+
artifactId,
|
|
46
|
+
note,
|
|
47
|
+
open = $bindable(false),
|
|
48
|
+
clips = [],
|
|
49
|
+
onClipsChange = undefined,
|
|
50
|
+
disabled = false,
|
|
51
|
+
maxClips = 10,
|
|
52
|
+
}: {
|
|
53
|
+
artifactId: string
|
|
54
|
+
note: ItemNoteProps
|
|
55
|
+
open?: boolean
|
|
56
|
+
/** The item's saved attachments (seed). */
|
|
57
|
+
clips?: NoteAttachment[]
|
|
58
|
+
onClipsChange?: (clips: NoteAttachment[]) => void
|
|
59
|
+
disabled?: boolean
|
|
60
|
+
maxClips?: number
|
|
61
|
+
} = $props()
|
|
62
|
+
|
|
63
|
+
const adapter = hostAdapter()
|
|
64
|
+
const upload = $derived(adapter.upload?.(artifactId))
|
|
65
|
+
const Chip = adapter.attachmentChip ?? AttachmentChip
|
|
66
|
+
|
|
67
|
+
// The local copy follows the saved list until this note changes it.
|
|
68
|
+
let local = $state<NoteAttachment[] | null>(null)
|
|
69
|
+
const shown = $derived(local ?? clips)
|
|
70
|
+
let uploading = $state(0)
|
|
71
|
+
let error = $state<string | null>(null)
|
|
72
|
+
let fileEl = $state<HTMLInputElement | null>(null)
|
|
73
|
+
const canAttach = $derived(Boolean(upload) && !disabled)
|
|
74
|
+
|
|
75
|
+
function setClips(next: NoteAttachment[]) {
|
|
76
|
+
local = next
|
|
77
|
+
onClipsChange?.(next)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function addFiles(files: Iterable<File>) {
|
|
81
|
+
if (!upload) return
|
|
82
|
+
for (const f of files) {
|
|
83
|
+
if (shown.length + uploading >= maxClips) {
|
|
84
|
+
error = `At most ${maxClips} attachments per note.`
|
|
85
|
+
break
|
|
86
|
+
}
|
|
87
|
+
uploading++
|
|
88
|
+
error = null
|
|
89
|
+
try {
|
|
90
|
+
const clip = await upload(f, "file")
|
|
91
|
+
setClips([...shown, clip])
|
|
92
|
+
} catch (e) {
|
|
93
|
+
error =
|
|
94
|
+
e instanceof Error && e.message
|
|
95
|
+
? e.message
|
|
96
|
+
: "Couldn't save that attachment. Please try again."
|
|
97
|
+
} finally {
|
|
98
|
+
uploading--
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function onPaste(e: ClipboardEvent) {
|
|
104
|
+
if (!canAttach) return
|
|
105
|
+
const files = [...(e.clipboardData?.files ?? [])]
|
|
106
|
+
if (files.length === 0) return
|
|
107
|
+
e.preventDefault()
|
|
108
|
+
void addFiles(files)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function onDrop(e: DragEvent) {
|
|
112
|
+
if (!canAttach) return
|
|
113
|
+
const files = [...(e.dataTransfer?.files ?? [])]
|
|
114
|
+
if (files.length === 0) return
|
|
115
|
+
e.preventDefault()
|
|
116
|
+
void addFiles(files)
|
|
117
|
+
}
|
|
118
|
+
</script>
|
|
119
|
+
|
|
120
|
+
<div
|
|
121
|
+
role="group"
|
|
122
|
+
aria-label="Review note"
|
|
123
|
+
ondragover={(e) => canAttach && e.preventDefault()}
|
|
124
|
+
ondrop={onDrop}
|
|
125
|
+
onpaste={onPaste}
|
|
126
|
+
data-item-note
|
|
127
|
+
>
|
|
128
|
+
<ReviewNote
|
|
129
|
+
bind:open
|
|
130
|
+
value={note.value ?? ""}
|
|
131
|
+
verdict={note.verdict ?? null}
|
|
132
|
+
options={note.options ?? []}
|
|
133
|
+
round={note.round ?? null}
|
|
134
|
+
saveFailed={note.saveFailed ?? false}
|
|
135
|
+
onSave={note.onSave}
|
|
136
|
+
onInput={note.onInput}
|
|
137
|
+
debounceMs={note.debounceMs}
|
|
138
|
+
returnFocusTo={note.returnFocusTo}
|
|
139
|
+
label={note.label ?? "Note"}
|
|
140
|
+
placeholder={note.placeholder ?? "What needs to change?"}
|
|
141
|
+
id={note.id}
|
|
142
|
+
disabled={note.disabled ?? disabled}
|
|
143
|
+
/>
|
|
144
|
+
{#if open}
|
|
145
|
+
{#if shown.length > 0 || uploading > 0}
|
|
146
|
+
<ul class="mt-2 flex flex-wrap items-center gap-1.5" aria-label="Attachments">
|
|
147
|
+
{#each shown as clip (clip.name)}
|
|
148
|
+
<li class="flex items-center gap-0.5">
|
|
149
|
+
<Chip {clip} />
|
|
150
|
+
{#if !disabled}
|
|
151
|
+
<button
|
|
152
|
+
type="button"
|
|
153
|
+
class="btn btn-ghost btn-xs btn-circle"
|
|
154
|
+
aria-label="Remove {clip.label ?? clip.name}"
|
|
155
|
+
onclick={() => setClips(shown.filter((c) => c !== clip))}>✕</button
|
|
156
|
+
>
|
|
157
|
+
{/if}
|
|
158
|
+
</li>
|
|
159
|
+
{/each}
|
|
160
|
+
{#if uploading > 0}
|
|
161
|
+
<li class="text-base-content/60 flex items-center gap-1.5 text-xs" role="status">
|
|
162
|
+
<span class="loading loading-spinner loading-xs"></span>Attaching…
|
|
163
|
+
</li>
|
|
164
|
+
{/if}
|
|
165
|
+
</ul>
|
|
166
|
+
{/if}
|
|
167
|
+
{#if error}
|
|
168
|
+
<p role="alert" class="text-error mt-1 text-xs">{error}</p>
|
|
169
|
+
{/if}
|
|
170
|
+
{#if canAttach}
|
|
171
|
+
<div class="mt-2 flex flex-wrap items-center gap-2">
|
|
172
|
+
<input
|
|
173
|
+
bind:this={fileEl}
|
|
174
|
+
type="file"
|
|
175
|
+
multiple
|
|
176
|
+
class="hidden"
|
|
177
|
+
onchange={(e) => {
|
|
178
|
+
const input = e.currentTarget as HTMLInputElement
|
|
179
|
+
void addFiles([...(input.files ?? [])])
|
|
180
|
+
input.value = ""
|
|
181
|
+
}}
|
|
182
|
+
/>
|
|
183
|
+
<button
|
|
184
|
+
type="button"
|
|
185
|
+
class="btn btn-ghost btn-xs rounded-field"
|
|
186
|
+
onclick={() => fileEl?.click()}
|
|
187
|
+
>
|
|
188
|
+
Attach file
|
|
189
|
+
</button>
|
|
190
|
+
</div>
|
|
191
|
+
{/if}
|
|
192
|
+
{/if}
|
|
193
|
+
</div>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { VerdictOption } from "../../actions/reviewKeys";
|
|
2
|
+
/** The per-item note's ReviewNote props (MOS ReviewComposer note mode). */
|
|
3
|
+
export interface ItemNoteProps {
|
|
4
|
+
value?: string;
|
|
5
|
+
verdict?: string | null;
|
|
6
|
+
options?: readonly VerdictOption[];
|
|
7
|
+
round?: string | number | null;
|
|
8
|
+
saveFailed?: boolean;
|
|
9
|
+
onSave?: (text: string, ctx: {
|
|
10
|
+
round: string | number | null;
|
|
11
|
+
}) => void | Promise<void>;
|
|
12
|
+
onInput?: (text: string) => void;
|
|
13
|
+
debounceMs?: number;
|
|
14
|
+
returnFocusTo?: () => HTMLElement | null | undefined;
|
|
15
|
+
id?: string;
|
|
16
|
+
label?: string;
|
|
17
|
+
placeholder?: string;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
}
|
|
20
|
+
import type { NoteAttachment } from "../types.js";
|
|
21
|
+
type $$ComponentProps = {
|
|
22
|
+
artifactId: string;
|
|
23
|
+
note: ItemNoteProps;
|
|
24
|
+
open?: boolean;
|
|
25
|
+
/** The item's saved attachments (seed). */
|
|
26
|
+
clips?: NoteAttachment[];
|
|
27
|
+
onClipsChange?: (clips: NoteAttachment[]) => void;
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
maxClips?: number;
|
|
30
|
+
};
|
|
31
|
+
declare const ItemNote: import("svelte").Component<$$ComponentProps, {}, "open">;
|
|
32
|
+
type ItemNote = ReturnType<typeof ItemNote>;
|
|
33
|
+
export default ItemNote;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* NoteToggle — the "Note" button beside a review item's VerdictControl
|
|
4
|
+
* (media-picker, diff-review). It shows / hides the item's jatui ReviewNote
|
|
5
|
+
* and carries a marker when the current round already has a note.
|
|
6
|
+
*/
|
|
7
|
+
import BlockIcon from "./BlockIcon.svelte"
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
open,
|
|
11
|
+
hasNote,
|
|
12
|
+
controls,
|
|
13
|
+
itemTitle,
|
|
14
|
+
/** mos-guqgs: a surface that cannot save renders the toggle visibly
|
|
15
|
+
* disabled — never clickable-but-useless. */
|
|
16
|
+
disabled = false,
|
|
17
|
+
onToggle,
|
|
18
|
+
}: {
|
|
19
|
+
open: boolean
|
|
20
|
+
hasNote: boolean
|
|
21
|
+
/** id of the note's textarea (ReviewNote `id`). */
|
|
22
|
+
controls: string
|
|
23
|
+
itemTitle: string
|
|
24
|
+
disabled?: boolean
|
|
25
|
+
onToggle: () => void
|
|
26
|
+
} = $props()
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<button
|
|
30
|
+
type="button"
|
|
31
|
+
class="btn btn-ghost btn-sm ml-auto gap-1 rounded-field px-2 {open
|
|
32
|
+
? 'text-base-content'
|
|
33
|
+
: 'text-base-content/75'}"
|
|
34
|
+
aria-expanded={open}
|
|
35
|
+
aria-controls={controls}
|
|
36
|
+
aria-label="Note for {itemTitle}"
|
|
37
|
+
aria-keyshortcuts="n"
|
|
38
|
+
{disabled}
|
|
39
|
+
data-has-note={hasNote ? "true" : undefined}
|
|
40
|
+
data-note-toggle
|
|
41
|
+
onclick={onToggle}
|
|
42
|
+
>
|
|
43
|
+
Note
|
|
44
|
+
{#if hasNote}
|
|
45
|
+
<BlockIcon name="sticky-note"
|
|
46
|
+
size={14}
|
|
47
|
+
stroke-width={2.25}
|
|
48
|
+
class="text-primary"
|
|
49
|
+
aria-hidden="true"
|
|
50
|
+
/><span class="sr-only">(has a note)</span>
|
|
51
|
+
{/if}
|
|
52
|
+
</button>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
open: boolean;
|
|
3
|
+
hasNote: boolean;
|
|
4
|
+
/** id of the note's textarea (ReviewNote `id`). */
|
|
5
|
+
controls: string;
|
|
6
|
+
itemTitle: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
onToggle: () => void;
|
|
9
|
+
};
|
|
10
|
+
declare const NoteToggle: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
11
|
+
type NoteToggle = ReturnType<typeof NoteToggle>;
|
|
12
|
+
export default NoteToggle;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* TeamChip — an item's team status (D7, ./team.ts), rendered like the
|
|
4
|
+
* checklist's Team column: quiet "Team" label, a tone icon, the words.
|
|
5
|
+
* `retest` adds the Retest chip: a new round is open (the team marked it
|
|
6
|
+
* ready, or a new version is up) and this viewer has not decided it yet.
|
|
7
|
+
* With no `team` only the Retest chip renders.
|
|
8
|
+
*/
|
|
9
|
+
import BlockIcon from "./BlockIcon.svelte"
|
|
10
|
+
import { ITEM_TEAM_LABEL, type ItemTeam } from "./team.js"
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
team,
|
|
14
|
+
retest = false,
|
|
15
|
+
}: {
|
|
16
|
+
team?: ItemTeam | undefined
|
|
17
|
+
retest?: boolean
|
|
18
|
+
} = $props()
|
|
19
|
+
|
|
20
|
+
const ICON = { ready: "rotate-ccw", in_progress: "circle-dot-dashed" } as const
|
|
21
|
+
const TONE = {
|
|
22
|
+
ready: "text-info",
|
|
23
|
+
in_progress: "text-base-content/70",
|
|
24
|
+
} as const
|
|
25
|
+
const since = $derived.by(() => {
|
|
26
|
+
if (!team?.readySince) return ""
|
|
27
|
+
const t = Date.parse(team.readySince)
|
|
28
|
+
return Number.isNaN(t)
|
|
29
|
+
? ""
|
|
30
|
+
: new Date(t).toLocaleString("en-US", {
|
|
31
|
+
month: "short",
|
|
32
|
+
day: "numeric",
|
|
33
|
+
hour: "numeric",
|
|
34
|
+
minute: "2-digit",
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<span class="inline-flex flex-wrap items-center gap-1.5 text-xs">
|
|
40
|
+
{#if team}
|
|
41
|
+
<span
|
|
42
|
+
class="inline-flex items-center gap-1"
|
|
43
|
+
data-team-status={team.status}
|
|
44
|
+
title="Team status: what the last publish recorded"
|
|
45
|
+
>
|
|
46
|
+
<span class="text-base-content/70"
|
|
47
|
+
>Team<span class="sr-only">{": "}</span></span
|
|
48
|
+
>
|
|
49
|
+
<BlockIcon
|
|
50
|
+
name={ICON[team.status]}
|
|
51
|
+
size={14}
|
|
52
|
+
stroke-width={2.25}
|
|
53
|
+
class={TONE[team.status]}
|
|
54
|
+
aria-hidden="true"
|
|
55
|
+
/>
|
|
56
|
+
<span class="font-medium text-base-content"
|
|
57
|
+
>{ITEM_TEAM_LABEL[team.status]}</span
|
|
58
|
+
>
|
|
59
|
+
</span>
|
|
60
|
+
{/if}
|
|
61
|
+
{#if retest}
|
|
62
|
+
<span
|
|
63
|
+
class="badge badge-sm badge-info"
|
|
64
|
+
data-retest-chip
|
|
65
|
+
title={since
|
|
66
|
+
? `Ready since ${since}: your earlier verdict is locked below, decide again`
|
|
67
|
+
: "A new round is up: your earlier verdict is locked below, decide again"}
|
|
68
|
+
>Retest</span
|
|
69
|
+
>
|
|
70
|
+
{/if}
|
|
71
|
+
</span>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ItemTeam } from "./team.js";
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
team?: ItemTeam | undefined;
|
|
4
|
+
retest?: boolean;
|
|
5
|
+
};
|
|
6
|
+
declare const TeamChip: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type TeamChip = ReturnType<typeof TeamChip>;
|
|
8
|
+
export default TeamChip;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading saved note attachments back out of a block's state (untrusted
|
|
3
|
+
* stored JSON) — ported from MOS modules/artifacts/noteAttachments.ts
|
|
4
|
+
* (readNoteAttachments) for the item-review blocks. The descriptors were
|
|
5
|
+
* validated by the HOST on the write path; this only re-shapes them, and an
|
|
6
|
+
* entry that is not a descriptor drops the whole list (never half a note).
|
|
7
|
+
*/
|
|
8
|
+
import type { NoteAttachment } from "../types.js";
|
|
9
|
+
export type { NoteAttachment };
|
|
10
|
+
export declare function readNoteAttachments(raw: unknown): NoteAttachment[] | undefined;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const ATTACHMENT_KINDS = [
|
|
2
|
+
"screen",
|
|
3
|
+
"voice",
|
|
4
|
+
"audio",
|
|
5
|
+
"video",
|
|
6
|
+
"image",
|
|
7
|
+
"file",
|
|
8
|
+
];
|
|
9
|
+
const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
10
|
+
export function readNoteAttachments(raw) {
|
|
11
|
+
if (!Array.isArray(raw) || raw.length === 0)
|
|
12
|
+
return undefined;
|
|
13
|
+
const out = [];
|
|
14
|
+
for (const entry of raw) {
|
|
15
|
+
if (!isObj(entry))
|
|
16
|
+
return undefined;
|
|
17
|
+
if (typeof entry.name !== "string" || typeof entry.url !== "string")
|
|
18
|
+
return undefined;
|
|
19
|
+
out.push({
|
|
20
|
+
name: entry.name,
|
|
21
|
+
url: entry.url,
|
|
22
|
+
contentType: typeof entry.contentType === "string" ? entry.contentType : "",
|
|
23
|
+
sizeBytes: typeof entry.sizeBytes === "number" ? entry.sizeBytes : 0,
|
|
24
|
+
kind: ATTACHMENT_KINDS.includes(entry.kind)
|
|
25
|
+
? entry.kind
|
|
26
|
+
: "file",
|
|
27
|
+
durationSeconds: typeof entry.durationSeconds === "number"
|
|
28
|
+
? entry.durationSeconds
|
|
29
|
+
: null,
|
|
30
|
+
transcript: typeof entry.transcript === "string" ? entry.transcript : null,
|
|
31
|
+
...(typeof entry.label === "string" && entry.label
|
|
32
|
+
? { label: entry.label }
|
|
33
|
+
: {}),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bulk verdicts for the review blocks (mos-nv4eb, mos-u8i35): accept OR
|
|
3
|
+
* reject many items in ONE save, and keep what Undo needs to put them back.
|
|
4
|
+
*
|
|
5
|
+
* Joe 2026-10-19: "bulk accept OR reject, and then go through and flip the
|
|
6
|
+
* outliers either way." Which items a bulk may touch is the block's call
|
|
7
|
+
* (still undecided, not a retest, shown under the active filter); this module
|
|
8
|
+
* only writes the verdict through the block's own round model, so a bulk
|
|
9
|
+
* entry is byte-for-byte what a single click would have saved.
|
|
10
|
+
*/
|
|
11
|
+
/** The `save` of a block's `useRounds()` binding (../review/rounds.ts). */
|
|
12
|
+
type RoundSave = (stored: unknown, patch: Record<string, unknown>, current: string | null | undefined) => Record<string, unknown>;
|
|
13
|
+
type Entry = {
|
|
14
|
+
verdict?: string;
|
|
15
|
+
at?: string;
|
|
16
|
+
};
|
|
17
|
+
export type BulkTarget = {
|
|
18
|
+
id: string;
|
|
19
|
+
/** The round the item is in (its team readySince / version). */
|
|
20
|
+
round: string | null;
|
|
21
|
+
/** Extra fields saved with the verdict (a pinned variant…). */
|
|
22
|
+
patch?: Record<string, unknown>;
|
|
23
|
+
};
|
|
24
|
+
export type BulkUndo<D> = {
|
|
25
|
+
/** The verdict the bulk cast. */
|
|
26
|
+
verdict: string;
|
|
27
|
+
/** The one `at` stamp every bulk-written entry carries. */
|
|
28
|
+
at: string;
|
|
29
|
+
/** Each touched item's entry before the bulk (undefined = it had none). */
|
|
30
|
+
prior: Record<string, D | undefined>;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Cast `verdict` on every target. A non-empty `note` is the shared note a
|
|
34
|
+
* bulk reject asks for ("Why? applies to all N"); it joins each target's own
|
|
35
|
+
* draft note rather than replacing it.
|
|
36
|
+
*/
|
|
37
|
+
export declare function decideMany<D extends Entry>(decisions: Record<string, D>, targets: readonly BulkTarget[], opts: {
|
|
38
|
+
verdict: string;
|
|
39
|
+
at: string;
|
|
40
|
+
note?: string;
|
|
41
|
+
save: RoundSave;
|
|
42
|
+
}): {
|
|
43
|
+
decisions: Record<string, D>;
|
|
44
|
+
undo: BulkUndo<D>;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Put every bulk-decided item back exactly as it was. An item the reviewer
|
|
48
|
+
* changed AFTER the bulk (a flip, a note — anything that re-stamps `at`)
|
|
49
|
+
* keeps that later change: Undo reverts the bulk, not their work.
|
|
50
|
+
*/
|
|
51
|
+
export declare function undoMany<D extends Entry>(decisions: Record<string, D>, undo: BulkUndo<D>): Record<string, D>;
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bulk verdicts for the review blocks (mos-nv4eb, mos-u8i35): accept OR
|
|
3
|
+
* reject many items in ONE save, and keep what Undo needs to put them back.
|
|
4
|
+
*
|
|
5
|
+
* Joe 2026-10-19: "bulk accept OR reject, and then go through and flip the
|
|
6
|
+
* outliers either way." Which items a bulk may touch is the block's call
|
|
7
|
+
* (still undecided, not a retest, shown under the active filter); this module
|
|
8
|
+
* only writes the verdict through the block's own round model, so a bulk
|
|
9
|
+
* entry is byte-for-byte what a single click would have saved.
|
|
10
|
+
*/
|
|
11
|
+
/** Join a shared bulk note onto a line's own draft note: typing is never
|
|
12
|
+
* thrown away, the shared reason is never lost. */
|
|
13
|
+
function joinNote(own, shared) {
|
|
14
|
+
const s = shared.trim();
|
|
15
|
+
if (!own?.trim())
|
|
16
|
+
return s;
|
|
17
|
+
return own.includes(s) ? own : `${own.trimEnd()}\n\n${s}`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Cast `verdict` on every target. A non-empty `note` is the shared note a
|
|
21
|
+
* bulk reject asks for ("Why? applies to all N"); it joins each target's own
|
|
22
|
+
* draft note rather than replacing it.
|
|
23
|
+
*/
|
|
24
|
+
export function decideMany(decisions, targets, opts) {
|
|
25
|
+
const next = { ...decisions };
|
|
26
|
+
const prior = {};
|
|
27
|
+
const shared = opts.note?.trim();
|
|
28
|
+
for (const { id, round, patch } of targets) {
|
|
29
|
+
const before = decisions[id];
|
|
30
|
+
prior[id] = before;
|
|
31
|
+
const own = before?.note;
|
|
32
|
+
next[id] = opts.save(before, {
|
|
33
|
+
...patch,
|
|
34
|
+
verdict: opts.verdict,
|
|
35
|
+
...(shared ? { note: joinNote(own, shared) } : {}),
|
|
36
|
+
at: opts.at,
|
|
37
|
+
}, round);
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
decisions: next,
|
|
41
|
+
undo: { verdict: opts.verdict, at: opts.at, prior },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Put every bulk-decided item back exactly as it was. An item the reviewer
|
|
46
|
+
* changed AFTER the bulk (a flip, a note — anything that re-stamps `at`)
|
|
47
|
+
* keeps that later change: Undo reverts the bulk, not their work.
|
|
48
|
+
*/
|
|
49
|
+
export function undoMany(decisions, undo) {
|
|
50
|
+
const next = { ...decisions };
|
|
51
|
+
for (const [id, before] of Object.entries(undo.prior)) {
|
|
52
|
+
const now = next[id];
|
|
53
|
+
if (!now || now.at !== undo.at || now.verdict !== undo.verdict)
|
|
54
|
+
continue;
|
|
55
|
+
if (before)
|
|
56
|
+
next[id] = before;
|
|
57
|
+
else
|
|
58
|
+
delete next[id];
|
|
59
|
+
}
|
|
60
|
+
return next;
|
|
61
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The item-review hooks of media-picker and diff-review (jatui-n5z1x):
|
|
3
|
+
* what the core census, still-open walk and Send read through
|
|
4
|
+
* `ArtifactBlockDef.itemReview` (types.ts BlockItemReview).
|
|
5
|
+
*
|
|
6
|
+
* A faithful port of MOS modules/artifacts/feedbackReview.ts at bad16b324 —
|
|
7
|
+
* `reviewItemsOf` (the non-checklist branch), `itemRoundOf`, `itemLabelOf`
|
|
8
|
+
* and `mediaThumbOf` — so a page Send here and a desk Send in MOS count
|
|
9
|
+
* identically (CONTRACT.md "Item-review rules"). Params and state are the
|
|
10
|
+
* AUTHORED / stored JSON (untrusted): nothing here may throw.
|
|
11
|
+
*/
|
|
12
|
+
import type { ReviewItem } from "../types.js";
|
|
13
|
+
type Rounds = {
|
|
14
|
+
isStale: (entry: unknown, current: string | null | undefined) => boolean;
|
|
15
|
+
};
|
|
16
|
+
/** An item's `team: { status: "ready", readySince }` round id, else null. */
|
|
17
|
+
export declare function teamReadySince(it: Record<string, unknown>): string | null;
|
|
18
|
+
/**
|
|
19
|
+
* The round an item is in: the team's readySince when it marked the item
|
|
20
|
+
* ready, else (media only) the version under review — `currentVersion`,
|
|
21
|
+
* else the first variant's `version`. null = the item has no rounds.
|
|
22
|
+
*/
|
|
23
|
+
export declare function itemRoundOf(kind: "media" | "diff", it: Record<string, unknown>): string | null;
|
|
24
|
+
/** The items of one media-picker / diff-review block, in its order. */
|
|
25
|
+
export declare function itemsOf(kind: "media" | "diff", rounds: Rounds, params: unknown, state: unknown): ReviewItem[];
|
|
26
|
+
/** An item's title for a jump card; the item id when absent. */
|
|
27
|
+
export declare function itemLabelOf(params: unknown, itemId: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* A media item's thumbnail: the round under review's first image (or a
|
|
30
|
+
* video's poster frame), else any image variant; undefined when none.
|
|
31
|
+
*/
|
|
32
|
+
export declare function mediaThumbOf(params: unknown, itemId: string): string | undefined;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const MAX_NOTE = 2000;
|
|
2
|
+
const ITEM_VERDICTS = new Map([
|
|
3
|
+
["approve", "approved"],
|
|
4
|
+
["pass", "approved"],
|
|
5
|
+
["accept", "approved"],
|
|
6
|
+
["redo", "needs-work"],
|
|
7
|
+
["modify", "needs-work"],
|
|
8
|
+
["fail", "needs-work"],
|
|
9
|
+
["blocked", "needs-work"],
|
|
10
|
+
["reject", "rejected"],
|
|
11
|
+
]);
|
|
12
|
+
const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
13
|
+
/** An item's `team: { status: "ready", readySince }` round id, else null. */
|
|
14
|
+
export function teamReadySince(it) {
|
|
15
|
+
const team = isObj(it.team) ? it.team : null;
|
|
16
|
+
return team?.status === "ready" &&
|
|
17
|
+
typeof team.readySince === "string" &&
|
|
18
|
+
team.readySince
|
|
19
|
+
? team.readySince
|
|
20
|
+
: null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The round an item is in: the team's readySince when it marked the item
|
|
24
|
+
* ready, else (media only) the version under review — `currentVersion`,
|
|
25
|
+
* else the first variant's `version`. null = the item has no rounds.
|
|
26
|
+
*/
|
|
27
|
+
export function itemRoundOf(kind, it) {
|
|
28
|
+
const ready = teamReadySince(it);
|
|
29
|
+
if (ready || kind !== "media")
|
|
30
|
+
return ready;
|
|
31
|
+
if (typeof it.currentVersion === "string" && it.currentVersion)
|
|
32
|
+
return it.currentVersion;
|
|
33
|
+
for (const v of Array.isArray(it.variants) ? it.variants : [])
|
|
34
|
+
if (isObj(v) && typeof v.version === "string" && v.version)
|
|
35
|
+
return v.version;
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
/** The items of one media-picker / diff-review block, in its order. */
|
|
39
|
+
export function itemsOf(kind, rounds, params, state) {
|
|
40
|
+
const p = isObj(params) ? params : {};
|
|
41
|
+
const st = isObj(state) ? state : {};
|
|
42
|
+
const out = [];
|
|
43
|
+
for (const it of Array.isArray(p.items) ? p.items : []) {
|
|
44
|
+
if (!isObj(it) || typeof it.id !== "string")
|
|
45
|
+
continue;
|
|
46
|
+
// An informational item asks no decision: context, not a unit — it
|
|
47
|
+
// never counts and can never block a Send.
|
|
48
|
+
if (it.informational === true)
|
|
49
|
+
continue;
|
|
50
|
+
const rawItem = st[it.id];
|
|
51
|
+
const d = isObj(rawItem) ? rawItem : null;
|
|
52
|
+
const raw = d && typeof d.verdict === "string" ? d.verdict.trim().toLowerCase() : "";
|
|
53
|
+
// A verdict (and its note) from an earlier round is not this round's
|
|
54
|
+
// answer — the block shows the item blank, so nothing here counts it.
|
|
55
|
+
const stale = d !== null && rounds.isStale(d, itemRoundOf(kind, it));
|
|
56
|
+
const verdict = stale ? null : (ITEM_VERDICTS.get(raw) ?? null);
|
|
57
|
+
const item = {
|
|
58
|
+
id: it.id,
|
|
59
|
+
verdict,
|
|
60
|
+
raw: verdict ? raw : null,
|
|
61
|
+
explicit: verdict !== null,
|
|
62
|
+
retest: verdict === null && (teamReadySince(it) !== null || stale),
|
|
63
|
+
};
|
|
64
|
+
if (d && !stale) {
|
|
65
|
+
if (typeof d.note === "string" && d.note.trim())
|
|
66
|
+
item.note = d.note.trim().slice(0, MAX_NOTE);
|
|
67
|
+
if (Array.isArray(d.attachments) && d.attachments.length > 0)
|
|
68
|
+
item.attachments = d.attachments;
|
|
69
|
+
}
|
|
70
|
+
out.push(item);
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
/** An item's title for a jump card; the item id when absent. */
|
|
75
|
+
export function itemLabelOf(params, itemId) {
|
|
76
|
+
const p = isObj(params) ? params : {};
|
|
77
|
+
for (const raw of Array.isArray(p.items) ? p.items : [])
|
|
78
|
+
if (isObj(raw) && raw.id === itemId && typeof raw.title === "string")
|
|
79
|
+
return raw.title;
|
|
80
|
+
return itemId;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* A media item's thumbnail: the round under review's first image (or a
|
|
84
|
+
* video's poster frame), else any image variant; undefined when none.
|
|
85
|
+
*/
|
|
86
|
+
export function mediaThumbOf(params, itemId) {
|
|
87
|
+
const p = isObj(params) ? params : {};
|
|
88
|
+
for (const raw of Array.isArray(p.items) ? p.items : []) {
|
|
89
|
+
if (!isObj(raw) || raw.id !== itemId)
|
|
90
|
+
continue;
|
|
91
|
+
const variants = (Array.isArray(raw.variants) ? raw.variants : []).filter(isObj);
|
|
92
|
+
const currentVersion = typeof raw.currentVersion === "string" && raw.currentVersion
|
|
93
|
+
? raw.currentVersion
|
|
94
|
+
: variants.find((v) => typeof v.version === "string" && v.version)
|
|
95
|
+
?.version;
|
|
96
|
+
const inRound = (v) => currentVersion === undefined ||
|
|
97
|
+
typeof v.version !== "string" ||
|
|
98
|
+
v.version === currentVersion;
|
|
99
|
+
const img = variants.find((v) => inRound(v) && v.kind === "image" && typeof v.src === "string") ??
|
|
100
|
+
// No image in this round: a stale round's image still orients better
|
|
101
|
+
// than an empty tile.
|
|
102
|
+
variants.find((v) => v.kind === "image" && typeof v.src === "string");
|
|
103
|
+
const poster = variants.find((v) => inRound(v) && v.kind === "video" && typeof v.poster === "string");
|
|
104
|
+
const hit = img ?? poster;
|
|
105
|
+
if (!hit)
|
|
106
|
+
return undefined;
|
|
107
|
+
if (typeof hit.poster === "string" && hit.kind === "video")
|
|
108
|
+
return hit.poster;
|
|
109
|
+
return typeof hit.src === "string" ? hit.src : undefined;
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|