@joewinke/jatui 0.1.11 → 0.1.20

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 (100) hide show
  1. package/README.md +123 -0
  2. package/package.json +2 -1
  3. package/src/lib/actions/railNav.ts +473 -0
  4. package/src/lib/components/AnnotationLayer.svelte +108 -0
  5. package/src/lib/components/AnnotationPanel.svelte +319 -0
  6. package/src/lib/components/AudioWaveform.svelte +9 -5
  7. package/src/lib/components/AvailabilityModal.svelte +7 -3
  8. package/src/lib/components/AvatarUpload.svelte +27 -4
  9. package/src/lib/components/BookingForm.svelte +11 -9
  10. package/src/lib/components/BurndownChart.svelte +778 -0
  11. package/src/lib/components/Button.svelte +10 -1
  12. package/src/lib/components/CalendarPicker.svelte +3 -3
  13. package/src/lib/components/Card.svelte +2 -2
  14. package/src/lib/components/ChipInput.svelte +8 -3
  15. package/src/lib/components/ColorSelector.svelte +17 -13
  16. package/src/lib/components/CommentThread.svelte +773 -0
  17. package/src/lib/components/ConfirmDialog.svelte +348 -0
  18. package/src/lib/components/ConfirmModal.svelte +78 -11
  19. package/src/lib/components/ContextMenu.svelte +59 -19
  20. package/src/lib/components/CountdownTimer.svelte +1 -1
  21. package/src/lib/components/DateRangePicker.svelte +6 -4
  22. package/src/lib/components/Drawer.svelte +36 -3
  23. package/src/lib/components/EntityPreviewCard.svelte +104 -0
  24. package/src/lib/components/FileDropzone.svelte +493 -0
  25. package/src/lib/components/FilePicker.svelte +83 -14
  26. package/src/lib/components/FileThumbnail.svelte +80 -0
  27. package/src/lib/components/FilterDropdown.svelte +11 -11
  28. package/src/lib/components/GPSTracker.svelte +202 -0
  29. package/src/lib/components/HunkDiffView.svelte +348 -0
  30. package/src/lib/components/ImageLightbox.svelte +274 -0
  31. package/src/lib/components/ImageUpload.svelte +58 -9
  32. package/src/lib/components/InlineEdit.svelte +6 -2
  33. package/src/lib/components/InputDialog.svelte +327 -0
  34. package/src/lib/components/KeyboardShortcutsOverlay.svelte +296 -0
  35. package/src/lib/components/LazyImage.svelte +1 -0
  36. package/src/lib/components/LinkShortener.svelte +1 -1
  37. package/src/lib/components/LoadingSpinner.svelte +6 -2
  38. package/src/lib/components/LocationMap.svelte +186 -0
  39. package/src/lib/components/MapView.svelte +341 -0
  40. package/src/lib/components/MarkupEditor.svelte +485 -0
  41. package/src/lib/components/MarkupOverlay.svelte +55 -0
  42. package/src/lib/components/MediaWorkbench.svelte +871 -0
  43. package/src/lib/components/MilestoneCard.svelte +1 -1
  44. package/src/lib/components/MilestoneTimeline.svelte +1 -1
  45. package/src/lib/components/Modal.svelte +39 -4
  46. package/src/lib/components/PDFViewer.svelte +105 -0
  47. package/src/lib/components/PdfThumbnail.svelte +3 -1
  48. package/src/lib/components/PhoneInput.svelte +1 -1
  49. package/src/lib/components/ResizablePanel.svelte +4 -4
  50. package/src/lib/components/SearchDropdown.svelte +26 -13
  51. package/src/lib/components/SelectInput.svelte +26 -4
  52. package/src/lib/components/SidebarUserFooter.svelte +1 -1
  53. package/src/lib/components/SignaturePad.svelte +8 -4
  54. package/src/lib/components/SmartImageEditor.svelte +720 -0
  55. package/src/lib/components/SortDropdown.svelte +9 -3
  56. package/src/lib/components/Sparkline.svelte +9 -0
  57. package/src/lib/components/StatusBadge.svelte +20 -18
  58. package/src/lib/components/TextArea.svelte +24 -5
  59. package/src/lib/components/TextInput.svelte +29 -6
  60. package/src/lib/components/ThemeSelector.svelte +15 -4
  61. package/src/lib/components/TimeSlotPicker.svelte +7 -7
  62. package/src/lib/components/UserAvatar.svelte +14 -1
  63. package/src/lib/components/VariablePicker.svelte +170 -0
  64. package/src/lib/components/VoicePlayer.svelte +4 -3
  65. package/src/lib/components/linked-columns/LinkedColumns.svelte +520 -0
  66. package/src/lib/components/markup.ts +287 -0
  67. package/src/lib/components/messaging/ChannelInfoModal.svelte +9 -9
  68. package/src/lib/components/messaging/ChannelList.svelte +1 -1
  69. package/src/lib/components/messaging/ChannelMembersModal.svelte +1 -1
  70. package/src/lib/components/messaging/CreateChannelModal.svelte +1 -1
  71. package/src/lib/components/messaging/DirectMessageList.svelte +1 -1
  72. package/src/lib/components/messaging/EmojiSelector.svelte +2 -1
  73. package/src/lib/components/messaging/MentionAutocomplete.svelte +1 -1
  74. package/src/lib/components/messaging/MessageAttachment.svelte +3 -3
  75. package/src/lib/components/messaging/MessageAttachmentUpload.svelte +3 -3
  76. package/src/lib/components/messaging/MessageInput.svelte +1 -1
  77. package/src/lib/components/messaging/MessageItem.svelte +6 -3
  78. package/src/lib/components/messaging/NotificationSettingsModal.svelte +1 -1
  79. package/src/lib/components/messaging/QuotedMessageDisplay.svelte +6 -1
  80. package/src/lib/components/messaging/StartDMModal.svelte +1 -1
  81. package/src/lib/components/pipeline/Pipeline.svelte +4 -4
  82. package/src/lib/components/pipeline/PipelineCard.svelte +1 -1
  83. package/src/lib/components/pipeline/PipelineColumn.svelte +8 -3
  84. package/src/lib/components/replay/ChapterTimeline.svelte +326 -0
  85. package/src/lib/components/session-nav/transcriptModel.ts +352 -0
  86. package/src/lib/index.ts +138 -0
  87. package/src/lib/stores/confirmDialog.svelte.ts +48 -0
  88. package/src/lib/stores/inputDialog.svelte.ts +51 -0
  89. package/src/lib/styles/rail.css +63 -0
  90. package/src/lib/types/annotation.ts +38 -0
  91. package/src/lib/types/comments.ts +97 -0
  92. package/src/lib/types/entityPreview.ts +45 -0
  93. package/src/lib/types/filePicker.ts +2 -0
  94. package/src/lib/types/googleMaps.d.ts +51 -0
  95. package/src/lib/types/maps.ts +43 -0
  96. package/src/lib/types/smartImageEditor.ts +39 -0
  97. package/src/lib/types/templateVars.ts +36 -0
  98. package/src/lib/utils/dateFormatters.ts +12 -10
  99. package/src/lib/utils/googleMapsLoader.ts +84 -0
  100. package/src/lib/utils/taskUtils.ts +21 -7
package/src/lib/index.ts CHANGED
@@ -1,9 +1,44 @@
1
+ // Actions
2
+ export { railNav, createRailNav, cycle } from './actions/railNav';
3
+ export type { RailNavOptions, RailNavController } from './actions/railNav';
4
+
5
+ // Components — Session primitives (from JAT IDE, jat-jj79f.16 G1)
6
+ export { default as LinkedColumns } from './components/linked-columns/LinkedColumns.svelte';
7
+ export type { ColumnLink } from './components/linked-columns/LinkedColumns.svelte';
8
+
9
+ export { default as ChapterTimeline } from './components/replay/ChapterTimeline.svelte';
10
+ export type { ChapterBlock } from './components/replay/ChapterTimeline.svelte';
11
+
12
+ // Data models — Session transcript (from JAT IDE, jat-jj79f.16 G1)
13
+ export {
14
+ buildLinkedSession,
15
+ buildLinkedSessionFromWork,
16
+ rawTypeToState,
17
+ sampleLinkedSession,
18
+ SAMPLE_TRANSCRIPT_LINES,
19
+ } from './components/session-nav/transcriptModel';
20
+ export type {
21
+ ItemKind,
22
+ TranscriptItem,
23
+ FileTouchedEntry,
24
+ SignalNode,
25
+ LinkedSession,
26
+ WorkBlockClient,
27
+ WorkTranscriptItemClient,
28
+ } from './components/session-nav/transcriptModel';
29
+
1
30
  // Components — Universal
2
31
  export { default as UserAvatar } from './components/UserAvatar.svelte';
3
32
  export { default as SidebarUserFooter } from './components/SidebarUserFooter.svelte';
4
33
  export { default as TaskTypeIcon } from './components/TaskTypeIcon.svelte';
5
34
 
6
35
  // Components — from JAT IDE
36
+ export { default as ConfirmDialog } from './components/ConfirmDialog.svelte';
37
+ export { showConfirm, confirmDialog, cancelDialog, getPending } from './stores/confirmDialog.svelte';
38
+ export type { ConfirmOptions } from './stores/confirmDialog.svelte';
39
+ export { default as InputDialog } from './components/InputDialog.svelte';
40
+ export { showInput, submitInput, cancelInput, getPendingInput } from './stores/inputDialog.svelte';
41
+ export type { InputOptions } from './stores/inputDialog.svelte';
7
42
  export { default as SearchDropdown } from './components/SearchDropdown.svelte';
8
43
  export { default as FilterDropdown } from './components/FilterDropdown.svelte';
9
44
  export { default as ContextMenu } from './components/ContextMenu.svelte';
@@ -13,6 +48,7 @@ export { default as DateRangePicker } from './components/DateRangePicker.svelte'
13
48
  export { default as SortDropdown } from './components/SortDropdown.svelte';
14
49
  export { default as InlineEdit } from './components/InlineEdit.svelte';
15
50
  export { default as FloatingActionBar } from './components/FloatingActionBar.svelte';
51
+ export { default as HunkDiffView } from './components/HunkDiffView.svelte';
16
52
 
17
53
  // Components — from Flush
18
54
  export { default as Button } from './components/Button.svelte';
@@ -34,6 +70,7 @@ export { default as ColorSelector } from './components/ColorSelector.svelte';
34
70
  export { default as ConfirmModal } from './components/ConfirmModal.svelte';
35
71
  export { default as CountdownTimer } from './components/CountdownTimer.svelte';
36
72
  export { default as ImageUpload } from './components/ImageUpload.svelte';
73
+ export { default as SmartImageEditor } from './components/SmartImageEditor.svelte';
37
74
  export { default as LazyImage } from './components/LazyImage.svelte';
38
75
  export { default as ResizablePanel } from './components/ResizablePanel.svelte';
39
76
  export { default as VoicePlayer } from './components/VoicePlayer.svelte';
@@ -42,15 +79,31 @@ export { default as SpeechForm } from './components/SpeechForm.svelte';
42
79
  // Components — from Headcount
43
80
  export { default as ThemeSelector } from './components/ThemeSelector.svelte';
44
81
  export { default as Sparkline } from './components/Sparkline.svelte';
82
+ export { default as BurndownChart } from './components/BurndownChart.svelte';
45
83
 
46
84
  // Components — from JST
47
85
  export { default as AvatarUpload } from './components/AvatarUpload.svelte';
48
86
  export { default as PdfThumbnail } from './components/PdfThumbnail.svelte';
49
87
  export { default as FilePicker } from './components/FilePicker.svelte';
88
+ export { default as FileDropzone } from './components/FileDropzone.svelte';
89
+ export { default as FileThumbnail } from './components/FileThumbnail.svelte';
90
+ export { default as EntityPreviewCard } from './components/EntityPreviewCard.svelte';
91
+
92
+ // EntityPreviewCard types
93
+ export type { EntityPreviewData, EntityPreviewStat, EntityPreviewBadge } from './types/entityPreview';
50
94
 
51
95
  // FilePicker types
52
96
  export type { FilePickerFile, FilePickerSelection } from './types/filePicker';
53
97
 
98
+ // FileDropzone types
99
+ export type { FileDropzoneFile } from './components/FileDropzone.svelte';
100
+
101
+ // Components — Document Templates (JST)
102
+ export { default as VariablePicker } from './components/VariablePicker.svelte';
103
+
104
+ // Template variable types
105
+ export type { TemplateVar, TemplateVarGroup } from './types/templateVars';
106
+
54
107
  // Components — Emoji
55
108
  export { default as EmojiPicker } from './components/EmojiPicker.svelte';
56
109
 
@@ -61,6 +114,9 @@ export type { EmojiEntry, EmojiGroup } from './data/emojis';
61
114
  // Components — from Meadow
62
115
  export { default as LinkShortener } from './components/LinkShortener.svelte';
63
116
 
117
+ // Components — Media
118
+ export { default as MediaWorkbench } from './components/MediaWorkbench.svelte';
119
+
64
120
  // Components — from Marduk
65
121
  export { default as SignaturePad } from './components/SignaturePad.svelte';
66
122
  export { default as MilestoneCard } from './components/MilestoneCard.svelte';
@@ -110,6 +166,14 @@ export {
110
166
  LOCATION_ICONS
111
167
  } from './types/booking';
112
168
 
169
+ // SmartImageEditor types
170
+ export type {
171
+ ImageVariantSpec,
172
+ ImageProcessingSpec,
173
+ VariantResult,
174
+ VariantState,
175
+ } from './types/smartImageEditor';
176
+
113
177
  // Utilities — date formatting
114
178
  export {
115
179
  normalizeTimestamp,
@@ -262,3 +326,77 @@ export {
262
326
  countMentions,
263
327
  setMentionUrlGenerator
264
328
  } from './utils/mentionParser';
329
+
330
+ // Components — Comments (universal threaded comments)
331
+ export { default as CommentThread } from './components/CommentThread.svelte';
332
+
333
+ // Comment types
334
+ export type {
335
+ Comment,
336
+ CommentEntityType,
337
+ CommentVisibility,
338
+ CommentCreateInput,
339
+ CommentCallbacks
340
+ } from './types/comments';
341
+ export { COMMENT_ENTITY_TYPES, COMMENT_VISIBILITIES } from './types/comments';
342
+
343
+ // Components — PDF Viewer (server-side PDFium WASM rendering, no client pdf.js)
344
+ export { default as PDFViewer } from './components/PDFViewer.svelte';
345
+
346
+ // Components — Document annotation suite (from Steelbridge FileManager)
347
+ export { default as MarkupEditor } from './components/MarkupEditor.svelte';
348
+ export { default as MarkupOverlay } from './components/MarkupOverlay.svelte';
349
+ export { default as ImageLightbox } from './components/ImageLightbox.svelte';
350
+ export { default as AnnotationLayer } from './components/AnnotationLayer.svelte';
351
+ export { default as AnnotationPanel } from './components/AnnotationPanel.svelte';
352
+
353
+ // Markup drawing utilities
354
+ export {
355
+ MARKUP_COLORS,
356
+ DEFAULT_STROKE_WIDTH,
357
+ renderShape,
358
+ renderAllShapes,
359
+ nextShapeId,
360
+ } from './components/markup';
361
+
362
+ // Markup + annotation types
363
+ export type {
364
+ MarkupTool,
365
+ Point,
366
+ MarkupShape,
367
+ ArrowShape,
368
+ RectangleShape,
369
+ EllipseShape,
370
+ CloudShape,
371
+ FreehandShape,
372
+ TextShape,
373
+ } from './components/markup';
374
+
375
+ export type {
376
+ Annotation,
377
+ AnnotationProfile,
378
+ AnnotationThread,
379
+ AnnotationCallbacks,
380
+ } from './types/annotation';
381
+
382
+ // Components — Keyboard Navigation
383
+ export { default as KeyboardShortcutsOverlay } from './components/KeyboardShortcutsOverlay.svelte';
384
+ export type {
385
+ KeyboardShortcut,
386
+ SectionEntry as KeyboardShortcutSectionEntry,
387
+ ShortcutSection as KeyboardShortcutSection,
388
+ } from './components/KeyboardShortcutsOverlay.svelte';
389
+
390
+ // Components — Google Maps (DispatchMap fork + location primitives)
391
+ export { default as MapView } from './components/MapView.svelte';
392
+ export { default as LocationMap } from './components/LocationMap.svelte';
393
+ export { default as GPSTracker } from './components/GPSTracker.svelte';
394
+ export type { MapJob, MapAgent } from './types/maps';
395
+
396
+ // Utilities — Google Maps loader
397
+ export {
398
+ loadGoogleMapsAPI,
399
+ isGoogleMapsLoaded,
400
+ isPlacesLoaded,
401
+ isGeometryLoaded,
402
+ } from './utils/googleMapsLoader';
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Global promise-based confirm dialog.
3
+ * Usage: const ok = await showConfirm({ title: '...', body: '...' })
4
+ */
5
+
6
+ export type ConfirmOptions = {
7
+ title: string;
8
+ body?: string;
9
+ confirmLabel?: string;
10
+ cancelLabel?: string;
11
+ danger?: boolean;
12
+ timeoutMs?: number; // auto-cancel after this ms; 0 = no timeout (default 8000)
13
+ };
14
+
15
+ type Pending = {
16
+ options: ConfirmOptions;
17
+ resolve: (result: boolean) => void;
18
+ };
19
+
20
+ let pending = $state<Pending | null>(null);
21
+
22
+ export function getPending(): Pending | null {
23
+ return pending;
24
+ }
25
+
26
+ export function showConfirm(options: ConfirmOptions): Promise<boolean> {
27
+ // If a dialog is already open, cancel it first.
28
+ if (pending) {
29
+ pending.resolve(false);
30
+ }
31
+ return new Promise<boolean>((resolve) => {
32
+ pending = { options, resolve };
33
+ });
34
+ }
35
+
36
+ export function confirmDialog(): void {
37
+ if (!pending) return;
38
+ const p = pending;
39
+ pending = null;
40
+ p.resolve(true);
41
+ }
42
+
43
+ export function cancelDialog(): void {
44
+ if (!pending) return;
45
+ const p = pending;
46
+ pending = null;
47
+ p.resolve(false);
48
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Global promise-based input dialog.
3
+ * Usage: const value = await showInput({ title: '...', placeholder: '...' })
4
+ * Returns the typed string if confirmed, null if cancelled.
5
+ */
6
+
7
+ export type InputOptions = {
8
+ title: string;
9
+ body?: string;
10
+ placeholder?: string;
11
+ confirmLabel?: string;
12
+ cancelLabel?: string;
13
+ timeoutMs?: number; // reserved; InputDialog never auto-cancels (user is typing)
14
+ validate?: (value: string) => string | null; // null = valid, string = error message
15
+ maxLength?: number;
16
+ };
17
+
18
+ type Pending = {
19
+ options: InputOptions;
20
+ resolve: (result: string | null) => void;
21
+ };
22
+
23
+ let pending = $state<Pending | null>(null);
24
+
25
+ export function getPendingInput(): Pending | null {
26
+ return pending;
27
+ }
28
+
29
+ export function showInput(options: InputOptions): Promise<string | null> {
30
+ // If a dialog is already open, cancel it first.
31
+ if (pending) {
32
+ pending.resolve(null);
33
+ }
34
+ return new Promise<string | null>((resolve) => {
35
+ pending = { options, resolve };
36
+ });
37
+ }
38
+
39
+ export function submitInput(value: string): void {
40
+ if (!pending) return;
41
+ const p = pending;
42
+ pending = null;
43
+ p.resolve(value);
44
+ }
45
+
46
+ export function cancelInput(): void {
47
+ if (!pending) return;
48
+ const p = pending;
49
+ pending = null;
50
+ p.resolve(null);
51
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * rail.css — keyboard rail-navigation primitives
3
+ *
4
+ * Provides focus-ring and value-change-pulse treatments for rail-nav
5
+ * components (vertical or horizontal keyboard-navigable lists where each
6
+ * rung carries a [data-rail-id] attribute).
7
+ *
8
+ * Usage: import once in your app's root layout or global stylesheet:
9
+ * @import '@joewinke/jatui/src/lib/styles/rail.css';
10
+ *
11
+ * Token: var(--color-primary) — DaisyUI v5 primary (resolves to the
12
+ * consuming app's active theme primary hue).
13
+ *
14
+ * Naming contract (must match the companion railNav Svelte action):
15
+ * - Active-rung focus treatment: [data-rail-id]:focus-visible selector
16
+ * AND .rail-focus utility class (both route to the same ring + tint).
17
+ * - Value-change one-shot pulse: .rail-cycled (added by the action,
18
+ * removed after the animation fires).
19
+ *
20
+ * Design rules enforced:
21
+ * - Ring + tint only — NO colored side-stripe (§6 Don't: border-l/r > 1px).
22
+ * - No box-shadow at rest; shadow only during the pulse keyframe.
23
+ * - Respects prefers-reduced-motion.
24
+ */
25
+
26
+ /* ─── Active-rung focus ring ───────────────────────────────────────────────
27
+ Applied via :focus-visible on the rung element (attribute-based, works
28
+ without adding a class) AND via .rail-focus (explicit class, same visual).
29
+ The ring is primary-tinted at 65% opacity; border-radius matches DaisyUI's
30
+ field radius token (falls back to 0.25rem). No side-stripe. No shadow at
31
+ rest (Flat-by-Default rule). ─────────────────────────────────────────── */
32
+
33
+ [data-rail-id]:focus-visible,
34
+ [data-rail-id]:has(:focus-visible),
35
+ .rail-focus {
36
+ outline: 2px solid color-mix(in oklch, var(--color-primary) 65%, transparent);
37
+ outline-offset: 2px;
38
+ border-radius: var(--radius-field, 0.25rem);
39
+ }
40
+
41
+ /* ─── Value-change pulse ────────────────────────────────────────────────────
42
+ One-shot animation applied by the railNav action when the rung's bound
43
+ value changes. Scales up 2.5% and blooms a brief primary-hued glow ring,
44
+ then returns to rest. Duration 260ms — fast enough to feel snappy,
45
+ long enough to register as intentional feedback.
46
+ Box-shadow (not outline) is used here so it can animate smoothly;
47
+ outline transitions are not interpolated by CSS. ─────────────────────── */
48
+
49
+ @keyframes rail-cycled {
50
+ 0% { transform: scale(1); box-shadow: 0 0 0 0 color-mix(in oklch, var(--color-primary) 0%, transparent); }
51
+ 35% { transform: scale(1.025); box-shadow: 0 0 0 3px color-mix(in oklch, var(--color-primary) 32%, transparent); }
52
+ 100% { transform: scale(1); box-shadow: 0 0 0 0 color-mix(in oklch, var(--color-primary) 0%, transparent); }
53
+ }
54
+
55
+ .rail-cycled {
56
+ animation: rail-cycled 0.26s cubic-bezier(0.22, 1, 0.36, 1);
57
+ }
58
+
59
+ @media (prefers-reduced-motion: reduce) {
60
+ .rail-cycled {
61
+ animation: none;
62
+ }
63
+ }
@@ -0,0 +1,38 @@
1
+ // Annotation types for document/image annotation suite
2
+ // Used by AnnotationLayer, AnnotationPanel, ImageLightbox
3
+
4
+ export interface Annotation {
5
+ id: string
6
+ user_id: string
7
+ x_percent: number
8
+ y_percent: number
9
+ content: string
10
+ parent_annotation_id: string | null
11
+ resolved: boolean
12
+ resolved_by: string | null
13
+ resolved_at: string | null
14
+ created_at: string
15
+ updated_at: string
16
+ }
17
+
18
+ export interface AnnotationProfile {
19
+ full_name: string | null
20
+ avatar_url: string | null
21
+ }
22
+
23
+ export interface AnnotationThread {
24
+ root: Annotation
25
+ replies: Annotation[]
26
+ }
27
+
28
+ /** Callbacks for AnnotationPanel async operations */
29
+ export interface AnnotationCallbacks {
30
+ /** Post a reply to a root annotation. Must return the new annotation or throw. */
31
+ onAddReply: (parentId: string, content: string) => Promise<void>
32
+ /** Toggle resolved state. */
33
+ onResolve: (annotationId: string, resolved: boolean) => Promise<void>
34
+ /** Delete an annotation (and its replies). */
35
+ onDelete: (annotationId: string) => Promise<void>
36
+ /** Edit annotation content. */
37
+ onEdit: (annotationId: string, content: string) => Promise<void>
38
+ }
@@ -0,0 +1,97 @@
1
+ // Threaded Comments Types
2
+ // Generic types for the CommentThread UI component — no framework or backend
3
+ // dependencies. Mirrors the JST `$lib/types/comments` projection so the
4
+ // component is a drop-in for any app whose server speaks the same shape.
5
+ //
6
+ // Threading is ONE level deep on the data side: a comment is either
7
+ // top-level (`parent_id === null`) or a single-level reply. The UI renders
8
+ // this as two visual levels (a comment and its replies).
9
+
10
+ /** Entity kinds a comment thread can hang off — mirrors the host DB CHECK. */
11
+ export type CommentEntityType =
12
+ | 'project'
13
+ | 'invoice'
14
+ | 'contract'
15
+ | 'asset'
16
+ | 'task'
17
+ | 'form_submission'
18
+ | 'milestone'
19
+ | 'contract_term'
20
+
21
+ export const COMMENT_ENTITY_TYPES: readonly CommentEntityType[] = [
22
+ 'project',
23
+ 'invoice',
24
+ 'contract',
25
+ 'asset',
26
+ 'task',
27
+ 'form_submission',
28
+ 'milestone',
29
+ 'contract_term'
30
+ ] as const
31
+
32
+ /** Internal = staff-only. Client = also visible in the customer portal. */
33
+ export type CommentVisibility = 'internal' | 'client'
34
+
35
+ export const COMMENT_VISIBILITIES: readonly CommentVisibility[] = [
36
+ 'internal',
37
+ 'client'
38
+ ] as const
39
+
40
+ export interface Comment {
41
+ id: string
42
+ team_id: string
43
+ entity_type: CommentEntityType
44
+ entity_id: string
45
+ author_id: string
46
+ /** Joined author display name (null if the profile is gone). */
47
+ author_name: string | null
48
+ /** Joined author avatar URL. */
49
+ author_avatar_url: string | null
50
+ body: string
51
+ /** null = top-level; otherwise the id of the top-level comment it replies to. */
52
+ parent_id: string | null
53
+ visibility: CommentVisibility
54
+ /** Set the first time the body is edited. */
55
+ edited_at: string | null
56
+ /** Set on soft-delete; body is replaced with "[deleted]". */
57
+ deleted_at: string | null
58
+ created_at: string
59
+ /** Number of replies (top-level rows only; always 0 for replies). */
60
+ reply_count: number
61
+ /**
62
+ * Replies are loaded on demand; this is [] in the paginated top-level
63
+ * listing and populated by the component when a thread is expanded.
64
+ */
65
+ replies: Comment[]
66
+ }
67
+
68
+ export interface CommentCreateInput {
69
+ entityType: CommentEntityType
70
+ entityId: string
71
+ body: string
72
+ /** When set, this comment is a reply to the given TOP-LEVEL comment. */
73
+ parentId?: string | null
74
+ /** Defaults to "internal". Clamped to the parent's visibility for replies. */
75
+ visibility?: CommentVisibility
76
+ }
77
+
78
+ /**
79
+ * Transport-agnostic data layer for `<CommentThread>`. The consuming app
80
+ * wires these to a server load / form action / fetch endpoint. The
81
+ * component never imports server code or knows how data is fetched.
82
+ */
83
+ export interface CommentCallbacks {
84
+ /** Persist a new comment (top-level or reply) and return the created row. */
85
+ submitComment: (input: CommentCreateInput) => Promise<Comment>
86
+ /** Edit an existing comment body; returns the updated row. */
87
+ editComment: (id: string, body: string) => Promise<Comment>
88
+ /** Soft-delete a comment. The row stays so replies remain anchored. */
89
+ deleteComment: (id: string) => Promise<void>
90
+ /** Fetch the replies for a top-level comment (oldest-first). */
91
+ loadReplies: (parentId: string) => Promise<Comment[]>
92
+ /**
93
+ * Optional: fetch the next page of top-level comments. When omitted, the
94
+ * "Load more" affordance is hidden.
95
+ */
96
+ loadMore?: (offset: number) => Promise<{ comments: Comment[]; total: number }>
97
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Entity preview card — the uniform shape an EntityPreviewCard renders and that
3
+ * a server-side resolver returns for any domain entity. One shape for every
4
+ * entity type so the card and hover-action stay generic and the same card backs
5
+ * hover-previews, @-mention unfurls, and link previews.
6
+ *
7
+ * Pure types — safe to import from both server (the resolver) and client (the
8
+ * card + action).
9
+ */
10
+
11
+ /** A small labelled stat shown in the card's footer grid (e.g. "Submissions" → 42). */
12
+ export interface EntityPreviewStat {
13
+ label: string
14
+ value: string | number
15
+ }
16
+
17
+ /** A coloured pill (status, type, …). `tone` maps to a DaisyUI semantic token. */
18
+ export interface EntityPreviewBadge {
19
+ label: string
20
+ tone?: "neutral" | "primary" | "info" | "success" | "warning" | "error"
21
+ }
22
+
23
+ export interface EntityPreviewData {
24
+ /** Canonical entity type slug (e.g. "base", "vendor", "form", "task"). */
25
+ type: string
26
+ id: string
27
+ /** Human label for the type, shown as the card's kicker (e.g. "Knowledge base"). */
28
+ typeLabel: string
29
+ /** Main title. */
30
+ title: string
31
+ /** Optional one-line subtitle under the title (e.g. a vendor's company). */
32
+ subtitle?: string | null
33
+ /** Optional longer summary / description (clamped in the card). */
34
+ summary?: string | null
35
+ /** og:image / avatar / thumbnail URL, when the entity has one. */
36
+ imageUrl?: string | null
37
+ /** A short emoji/text glyph fallback when there's no image. */
38
+ icon?: string | null
39
+ /** Up to ~4 labelled stats. */
40
+ stats?: EntityPreviewStat[]
41
+ /** Up to ~3 status/type pills. */
42
+ badges?: EntityPreviewBadge[]
43
+ /** The admin detail route for the entity (the card links here). */
44
+ href?: string | null
45
+ }
@@ -5,6 +5,8 @@ export interface FilePickerFile {
5
5
  updated_at: string
6
6
  metadata: Record<string, any>
7
7
  scope: "personal" | "team"
8
+ /** Publicly accessible URL (no auth required). When present, used instead of the /api/files proxy URL. */
9
+ publicUrl?: string
8
10
  }
9
11
 
10
12
  export interface FilePickerSelection {
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Minimal ambient declarations for Google Maps JS API loaded at runtime.
3
+ * Prevents "Property 'google' does not exist on Window" TS errors.
4
+ * Components using the Google Maps API operate with `any` for advanced
5
+ * marker types until @types/google.maps is added as a dev dependency.
6
+ */
7
+
8
+ declare namespace google {
9
+ namespace maps {
10
+ class Map {
11
+ constructor(el: HTMLElement, options: object);
12
+ fitBounds(bounds: any): void;
13
+ getZoom(): number | undefined;
14
+ setZoom(zoom: number): void;
15
+ setCenter(center: object): void;
16
+ }
17
+ class InfoWindow {
18
+ constructor(options: object);
19
+ open(map: any, marker: any): void;
20
+ }
21
+ class Circle {
22
+ constructor(options: object);
23
+ setMap(map: any): void;
24
+ }
25
+ class LatLngBounds {
26
+ extend(position: object): void;
27
+ }
28
+ const MapTypeId: { ROADMAP: string };
29
+ const event: {
30
+ addListener(instance: any, event: string, fn: () => void): any;
31
+ removeListener(listener: any): void;
32
+ clearInstanceListeners(instance: any): void;
33
+ };
34
+ namespace marker {
35
+ class AdvancedMarkerElement {
36
+ constructor(options: object);
37
+ map: any;
38
+ position: any;
39
+ addListener(event: string, fn: () => void): void;
40
+ }
41
+ }
42
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
+ const places: any;
44
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
+ const geometry: any;
46
+ }
47
+ }
48
+
49
+ interface Window {
50
+ google?: typeof google;
51
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Types for MapView, LocationMap, and GPSTracker components.
3
+ */
4
+
5
+ export interface MapJob {
6
+ id: string;
7
+ status: string;
8
+ type?: string;
9
+ priority?: string;
10
+ description?: string;
11
+ scheduled_at?: string;
12
+ customer?: {
13
+ name?: string;
14
+ phone?: string;
15
+ };
16
+ location?: {
17
+ address?: string;
18
+ latitude?: number;
19
+ longitude?: number;
20
+ };
21
+ }
22
+
23
+ export interface MapAgent {
24
+ id: string;
25
+ name: string;
26
+ email?: string;
27
+ avatarUrl?: string;
28
+ status?: string;
29
+ capacity?: string;
30
+ specializations?: string[];
31
+ vehicle?: { name?: string };
32
+ /** Most recent GPS fix */
33
+ location?: {
34
+ latitude?: number;
35
+ longitude?: number;
36
+ age_seconds?: number;
37
+ };
38
+ /** Active dispatches used as fallback location */
39
+ dispatches?: Array<{
40
+ job_id: string;
41
+ status: string;
42
+ }>;
43
+ }
@@ -0,0 +1,39 @@
1
+ export interface ImageVariantSpec {
2
+ name: string
3
+ width: number
4
+ height: number
5
+ fit?: "contain" | "cover" | "fill"
6
+ /** CSS hex color or "transparent". Transparent background when omitted. */
7
+ background?: string
8
+ /**
9
+ * Optional Gemini edit instruction run on the (background-removed) source
10
+ * before Sharp's deterministic resize. Used to intelligently re-frame the
11
+ * variant — e.g. crop a logo down to just the brand mark, or compose an OG
12
+ * card. Falls back to Sharp-only if the Gemini call fails.
13
+ */
14
+ aiPrompt?: string
15
+ }
16
+
17
+ export interface ImageProcessingSpec {
18
+ removeBackground?: boolean
19
+ aspectRatio?: string
20
+ variants: ImageVariantSpec[]
21
+ }
22
+
23
+ export interface VariantResult {
24
+ name: string
25
+ url: string
26
+ width: number
27
+ height: number
28
+ backgroundRemovalFailed?: boolean
29
+ /** True when this variant had an aiPrompt but the Gemini edit call failed
30
+ * and we fell back to Sharp-only. The result is still uploaded; this is a
31
+ * non-blocking signal that the smart re-frame didn't happen. */
32
+ aiEditFailed?: boolean
33
+ }
34
+
35
+ export type VariantState =
36
+ | { status: "idle" }
37
+ | { status: "loading" }
38
+ | { status: "done"; result: VariantResult }
39
+ | { status: "error"; message: string }