@autumnsgrove/groveengine 0.6.2 → 0.6.4

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/LICENSE +378 -0
  2. package/dist/auth/jwt.d.ts +10 -4
  3. package/dist/auth/jwt.js +18 -4
  4. package/dist/auth/session.d.ts +22 -15
  5. package/dist/auth/session.js +35 -16
  6. package/dist/components/admin/GutterManager.svelte +81 -139
  7. package/dist/components/admin/GutterManager.svelte.d.ts +6 -6
  8. package/dist/components/admin/MarkdownEditor.svelte +80 -23
  9. package/dist/components/admin/MarkdownEditor.svelte.d.ts +14 -8
  10. package/dist/components/admin/composables/useAmbientSounds.svelte.d.ts +52 -2
  11. package/dist/components/admin/composables/useAmbientSounds.svelte.js +38 -4
  12. package/dist/components/admin/composables/useCommandPalette.svelte.d.ts +80 -10
  13. package/dist/components/admin/composables/useCommandPalette.svelte.js +45 -5
  14. package/dist/components/admin/composables/useDraftManager.svelte.d.ts +76 -14
  15. package/dist/components/admin/composables/useDraftManager.svelte.js +44 -10
  16. package/dist/components/admin/composables/useEditorTheme.svelte.d.ts +168 -2
  17. package/dist/components/admin/composables/useEditorTheme.svelte.js +40 -7
  18. package/dist/components/admin/composables/useSlashCommands.svelte.d.ts +94 -22
  19. package/dist/components/admin/composables/useSlashCommands.svelte.js +58 -9
  20. package/dist/components/admin/composables/useSnippets.svelte.d.ts +51 -2
  21. package/dist/components/admin/composables/useSnippets.svelte.js +35 -3
  22. package/dist/components/admin/composables/useWritingSession.svelte.d.ts +64 -6
  23. package/dist/components/admin/composables/useWritingSession.svelte.js +42 -5
  24. package/dist/components/custom/ContentWithGutter.svelte +53 -23
  25. package/dist/components/custom/ContentWithGutter.svelte.d.ts +6 -14
  26. package/dist/components/custom/GutterItem.svelte +1 -1
  27. package/dist/components/custom/LeftGutter.svelte +43 -13
  28. package/dist/components/custom/LeftGutter.svelte.d.ts +6 -6
  29. package/dist/config/ai-models.js +1 -1
  30. package/dist/groveauth/client.js +11 -11
  31. package/dist/index.d.ts +3 -1
  32. package/dist/index.js +2 -2
  33. package/dist/server/logger.d.ts +74 -26
  34. package/dist/server/logger.js +133 -184
  35. package/dist/server/services/cache.js +1 -10
  36. package/dist/ui/components/charts/ActivityOverview.svelte +14 -3
  37. package/dist/ui/components/charts/ActivityOverview.svelte.d.ts +10 -7
  38. package/dist/ui/components/charts/RepoBreakdown.svelte +9 -3
  39. package/dist/ui/components/charts/RepoBreakdown.svelte.d.ts +12 -11
  40. package/dist/ui/components/charts/Sparkline.svelte +18 -7
  41. package/dist/ui/components/charts/Sparkline.svelte.d.ts +21 -2
  42. package/dist/ui/components/gallery/ImageGallery.svelte +12 -8
  43. package/dist/ui/components/gallery/ImageGallery.svelte.d.ts +2 -2
  44. package/dist/ui/components/gallery/Lightbox.svelte +5 -2
  45. package/dist/ui/components/gallery/ZoomableImage.svelte +8 -5
  46. package/dist/ui/components/primitives/accordion/index.d.ts +1 -1
  47. package/dist/ui/components/primitives/input/input.svelte.d.ts +1 -1
  48. package/dist/ui/components/primitives/tabs/index.d.ts +1 -1
  49. package/dist/ui/components/primitives/textarea/textarea.svelte.d.ts +1 -1
  50. package/dist/ui/components/ui/Button.svelte +5 -0
  51. package/dist/ui/components/ui/Button.svelte.d.ts +4 -1
  52. package/dist/ui/components/ui/Input.svelte +4 -0
  53. package/dist/ui/components/ui/Input.svelte.d.ts +3 -1
  54. package/dist/ui/components/ui/Logo.svelte +86 -0
  55. package/dist/ui/components/ui/Logo.svelte.d.ts +25 -0
  56. package/dist/ui/components/ui/LogoLoader.svelte +71 -0
  57. package/dist/ui/components/ui/LogoLoader.svelte.d.ts +9 -0
  58. package/dist/ui/components/ui/index.d.ts +2 -0
  59. package/dist/ui/components/ui/index.js +2 -0
  60. package/dist/ui/tailwind.preset.js +8 -8
  61. package/dist/utils/api.js +2 -1
  62. package/dist/utils/debounce.d.ts +4 -3
  63. package/dist/utils/debounce.js +10 -6
  64. package/dist/utils/gallery.d.ts +58 -32
  65. package/dist/utils/gallery.js +111 -129
  66. package/dist/utils/gutter.d.ts +47 -26
  67. package/dist/utils/gutter.js +116 -124
  68. package/dist/utils/imageProcessor.d.ts +66 -19
  69. package/dist/utils/imageProcessor.js +31 -10
  70. package/dist/utils/index.d.ts +11 -11
  71. package/dist/utils/index.js +4 -3
  72. package/dist/utils/json.js +1 -1
  73. package/dist/utils/markdown.d.ts +183 -103
  74. package/dist/utils/markdown.js +517 -678
  75. package/dist/utils/sanitize.d.ts +22 -12
  76. package/dist/utils/sanitize.js +268 -282
  77. package/dist/utils/validation.js +4 -3
  78. package/package.json +23 -23
  79. package/static/fonts/alagard.ttf +0 -0
@@ -1,9 +1,9 @@
1
1
  <script>
2
2
  import { marked } from "marked";
3
3
  import { onMount, tick } from "svelte";
4
- import { sanitizeMarkdown } from "../../utils/sanitize.js";
4
+ import { sanitizeMarkdown } from "../../utils/sanitize";
5
5
  import "../../styles/content.css";
6
- import { Button, Input } from '../../ui';
6
+ import { Button, Input, Logo } from '../../ui';
7
7
  import Dialog from "../../ui/components/ui/Dialog.svelte";
8
8
 
9
9
  // Import composables
@@ -19,22 +19,32 @@
19
19
  useCommandPalette,
20
20
  } from "./composables/index.js";
21
21
 
22
+ /**
23
+ * @typedef {Object} StoredDraft
24
+ * @property {string} content
25
+ * @property {number} savedAt
26
+ * @property {number} [wordCount]
27
+ */
28
+
22
29
  // Props
23
30
  let {
24
31
  content = $bindable(""),
25
32
  onSave = () => {},
26
33
  saving = false,
27
34
  readonly = false,
28
- draftKey = null,
29
- onDraftRestored = () => {},
35
+ draftKey = /** @type {string | null} */ (null),
36
+ onDraftRestored = /** @type {(draft: StoredDraft) => void} */ (() => {}),
30
37
  previewTitle = "",
31
38
  previewDate = "",
32
- previewTags = [],
39
+ previewTags = /** @type {string[]} */ ([]),
33
40
  } = $props();
34
41
 
35
42
  // Core refs and state
43
+ /** @type {HTMLTextAreaElement | null} */
36
44
  let textareaRef = $state(null);
45
+ /** @type {HTMLElement | null} */
37
46
  let previewRef = $state(null);
47
+ /** @type {HTMLElement | null} */
38
48
  let lineNumbersRef = $state(null);
39
49
  let showPreview = $state(true);
40
50
  let cursorLine = $state(1);
@@ -46,6 +56,7 @@
46
56
  let isDragging = $state(false);
47
57
  let isUploading = $state(false);
48
58
  let uploadProgress = $state("");
59
+ /** @type {string | null} */
49
60
  let uploadError = $state(null);
50
61
 
51
62
  // Full preview mode
@@ -71,10 +82,11 @@
71
82
  getWordCount: () => wordCount,
72
83
  });
73
84
 
85
+ // svelte-ignore state_referenced_locally - draftKey, readonly, onDraftRestored don't change during lifecycle
74
86
  const draftManager = useDraftManager({
75
87
  draftKey,
76
88
  getContent: () => content,
77
- setContent: (c) => (content = c),
89
+ setContent: (/** @type {string} */ c) => (content = c),
78
90
  onDraftRestored,
79
91
  readonly,
80
92
  });
@@ -82,7 +94,7 @@
82
94
  const slashCommands = useSlashCommands({
83
95
  getTextareaRef: () => textareaRef,
84
96
  getContent: () => content,
85
- setContent: (c) => (content = c),
97
+ setContent: (/** @type {string} */ c) => (content = c),
86
98
  getSnippets: () => snippetsManager.snippets,
87
99
  onOpenSnippetsModal: () => snippetsManager.openModal(),
88
100
  });
@@ -116,7 +128,7 @@
116
128
  let wordCount = $derived(content.trim() ? content.trim().split(/\s+/).length : 0);
117
129
  let charCount = $derived(content.length);
118
130
  let lineCount = $derived(content.split("\n").length);
119
- let previewHtml = $derived(content ? sanitizeMarkdown(marked.parse(content)) : "");
131
+ let previewHtml = $derived(content ? sanitizeMarkdown(/** @type {string} */ (marked.parse(content, { async: false }))) : "");
120
132
 
121
133
  let readingTime = $derived.by(() => {
122
134
  const minutes = Math.ceil(wordCount / 200);
@@ -172,6 +184,7 @@
172
184
  return availableAnchors;
173
185
  }
174
186
 
187
+ /** @param {string} name */
175
188
  export function insertAnchor(name) {
176
189
  insertAtCursor(`<!-- anchor:${name} -->\n`);
177
190
  }
@@ -195,6 +208,7 @@
195
208
  }
196
209
 
197
210
  // Keyboard handlers
211
+ /** @param {KeyboardEvent} e */
198
212
  function handleKeydown(e) {
199
213
  // Escape key handling
200
214
  if (e.key === "Escape") {
@@ -213,9 +227,10 @@
213
227
  }
214
228
 
215
229
  // Slash commands trigger
216
- if (e.key === "/" && !slashCommands.isOpen) {
230
+ if (e.key === "/" && !slashCommands.isOpen && textareaRef) {
217
231
  const pos = textareaRef.selectionStart;
218
- if (slashCommands.shouldTrigger("/", pos, content)) {
232
+ // Only trigger at start of line or after whitespace
233
+ if (pos === 0 || /\s$/.test(content.substring(0, pos))) {
219
234
  setTimeout(() => slashCommands.open(), 0);
220
235
  }
221
236
  }
@@ -254,13 +269,15 @@
254
269
  }
255
270
 
256
271
  // Tab for indentation
257
- if (e.key === "Tab") {
272
+ if (e.key === "Tab" && textareaRef) {
258
273
  e.preventDefault();
259
274
  const start = textareaRef.selectionStart;
260
275
  const end = textareaRef.selectionEnd;
261
276
  content = content.substring(0, start) + " " + content.substring(end);
262
277
  setTimeout(() => {
263
- textareaRef.selectionStart = textareaRef.selectionEnd = start + 2;
278
+ if (textareaRef) {
279
+ textareaRef.selectionStart = textareaRef.selectionEnd = start + 2;
280
+ }
264
281
  }, 0);
265
282
  }
266
283
 
@@ -283,6 +300,7 @@
283
300
  }
284
301
  }
285
302
 
303
+ /** @param {KeyboardEvent} e */
286
304
  function handleGlobalKeydown(e) {
287
305
  if (e.key === "Escape") {
288
306
  if (ambientSounds.showPanel) {
@@ -327,6 +345,10 @@
327
345
  }
328
346
 
329
347
  // Text manipulation helpers
348
+ /**
349
+ * @param {string} before
350
+ * @param {string} after
351
+ */
330
352
  async function wrapSelection(before, after) {
331
353
  if (!textareaRef || isUpdating) return;
332
354
  isUpdating = true;
@@ -347,6 +369,7 @@
347
369
  isUpdating = false;
348
370
  }
349
371
 
372
+ /** @param {string} text */
350
373
  async function insertAtCursor(text) {
351
374
  if (!textareaRef || isUpdating) return;
352
375
  isUpdating = true;
@@ -365,6 +388,7 @@
365
388
  }
366
389
 
367
390
  // Toolbar actions
391
+ /** @param {number} level */
368
392
  function insertHeading(level) {
369
393
  insertAtCursor("#".repeat(level) + " ");
370
394
  }
@@ -429,6 +453,7 @@
429
453
  });
430
454
 
431
455
  // Drag and drop handlers
456
+ /** @param {DragEvent} e */
432
457
  function handleDragEnter(e) {
433
458
  e.preventDefault();
434
459
  if (readonly) return;
@@ -437,6 +462,7 @@
437
462
  }
438
463
  }
439
464
 
465
+ /** @param {DragEvent} e */
440
466
  function handleDragOver(e) {
441
467
  e.preventDefault();
442
468
  if (readonly) return;
@@ -446,13 +472,16 @@
446
472
  }
447
473
  }
448
474
 
475
+ /** @param {DragEvent} e */
449
476
  function handleDragLeave(e) {
450
477
  e.preventDefault();
451
- if (!e.currentTarget.contains(e.relatedTarget)) {
478
+ const target = /** @type {HTMLElement} */ (e.currentTarget);
479
+ if (!target.contains(/** @type {Node | null} */ (e.relatedTarget))) {
452
480
  isDragging = false;
453
481
  }
454
482
  }
455
483
 
484
+ /** @param {DragEvent} e */
456
485
  async function handleDrop(e) {
457
486
  e.preventDefault();
458
487
  isDragging = false;
@@ -472,6 +501,7 @@
472
501
  }
473
502
  }
474
503
 
504
+ /** @param {File} file */
475
505
  async function uploadImage(file) {
476
506
  isUploading = true;
477
507
  uploadProgress = `Uploading ${file.name}...`;
@@ -499,7 +529,7 @@
499
529
 
500
530
  uploadProgress = "";
501
531
  } catch (err) {
502
- uploadError = err.message;
532
+ uploadError = err instanceof Error ? err.message : String(err);
503
533
  setTimeout(() => (uploadError = null), 5000);
504
534
  } finally {
505
535
  isUploading = false;
@@ -507,6 +537,7 @@
507
537
  }
508
538
  }
509
539
 
540
+ /** @param {ClipboardEvent} e */
510
541
  function handlePaste(e) {
511
542
  if (readonly) return;
512
543
 
@@ -528,6 +559,7 @@
528
559
  }
529
560
 
530
561
  // Command palette execution
562
+ /** @param {number} index */
531
563
  function executePaletteCommand(index) {
532
564
  const cmd = filteredPaletteCommands[index];
533
565
  if (cmd && cmd.action) {
@@ -558,6 +590,7 @@
558
590
  class:zen-mode={isZenMode}
559
591
  class:campfire-mode={writingSession.isCampfireActive}
560
592
  aria-label="Markdown editor with live preview"
593
+ role="application"
561
594
  ondragenter={handleDragEnter}
562
595
  ondragover={handleDragOver}
563
596
  ondragleave={handleDragLeave}
@@ -717,6 +750,7 @@
717
750
  <div class="preview-panel">
718
751
  <div class="preview-header">
719
752
  <span class="preview-label">:: preview</span>
753
+ <Logo class="preview-logo" />
720
754
  </div>
721
755
  <div class="preview-content" bind:this={previewRef}>
722
756
  {#if previewHtml}
@@ -760,7 +794,7 @@
760
794
  onclick={() => ambientSounds.togglePanel()}
761
795
  title="Ambient sounds"
762
796
  >
763
- [{soundLibrary[ambientSounds.currentSound]?.name || "snd"}]{#if ambientSounds.enabled}<span class="sound-wave">~</span>{/if}
797
+ [{soundLibrary[/** @type {keyof typeof soundLibrary} */ (ambientSounds.currentSound)]?.name || "snd"}]{#if ambientSounds.enabled}<span class="sound-wave">~</span>{/if}
764
798
  </button>
765
799
  <span class="status-divider">|</span>
766
800
  {#if editorSettings.typewriterMode}
@@ -800,14 +834,16 @@
800
834
 
801
835
  <!-- Command Palette -->
802
836
  {#if commandPalette.isOpen}
803
- <div class="command-palette-overlay" onclick={() => commandPalette.close()}>
837
+ <!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
838
+ <div class="command-palette-overlay" onclick={() => commandPalette.close()} role="dialog" aria-modal="true" aria-label="Command palette">
839
+ <!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
804
840
  <div class="command-palette" onclick={(e) => e.stopPropagation()}>
805
841
  <input
806
842
  type="text"
807
843
  class="command-palette-input"
808
844
  placeholder="> type a command..."
809
845
  value={commandPalette.query}
810
- oninput={(e) => commandPalette.setQuery(e.target.value)}
846
+ oninput={(e) => commandPalette.setQuery(/** @type {HTMLInputElement} */ (e.target).value)}
811
847
  onkeydown={(e) => {
812
848
  if (e.key === "ArrowDown") {
813
849
  e.preventDefault();
@@ -860,9 +896,10 @@
860
896
  {/if}
861
897
 
862
898
  <!-- Snippets Modal -->
863
- <Dialog bind:open={snippetsManager.modal.open}>
864
- <h3 slot="title">:: {snippetsManager.modal.editingId ? "edit snippet" : "new snippet"}</h3>
865
-
899
+ <Dialog
900
+ bind:open={snippetsManager.modal.open}
901
+ title={`:: ${snippetsManager.modal.editingId ? "edit snippet" : "new snippet"}`}
902
+ >
866
903
  <div class="snippets-modal-body">
867
904
  <div class="snippets-form">
868
905
  <div class="snippet-field">
@@ -898,7 +935,10 @@
898
935
 
899
936
  <div class="snippet-actions">
900
937
  {#if snippetsManager.modal.editingId}
901
- <Button variant="danger" onclick={() => snippetsManager.deleteSnippet(snippetsManager.modal.editingId)}>
938
+ <Button variant="danger" onclick={() => {
939
+ const id = snippetsManager.modal.editingId;
940
+ if (id) snippetsManager.deleteSnippet(id);
941
+ }}>
902
942
  [<span class="key">d</span>elete]
903
943
  </Button>
904
944
  {/if}
@@ -971,7 +1011,10 @@
971
1011
  max="1"
972
1012
  step="0.05"
973
1013
  value={ambientSounds.volume}
974
- oninput={(e) => ambientSounds.setVolume(parseFloat(e.target.value))}
1014
+ oninput={(e) => {
1015
+ const target = /** @type {HTMLInputElement} */ (e.target);
1016
+ ambientSounds.setVolume(parseFloat(target.value));
1017
+ }}
975
1018
  class="volume-slider"
976
1019
  />
977
1020
  </label>
@@ -994,7 +1037,8 @@
994
1037
 
995
1038
  <!-- Full Preview Modal -->
996
1039
  {#if showFullPreview}
997
- <div class="full-preview-modal" role="dialog" aria-modal="true">
1040
+ <div class="full-preview-modal" role="dialog" aria-modal="true" aria-label="Full article preview">
1041
+ <!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
998
1042
  <div class="full-preview-backdrop" onclick={() => (showFullPreview = false)}></div>
999
1043
  <div class="full-preview-container">
1000
1044
  <header class="full-preview-header">
@@ -1357,6 +1401,9 @@
1357
1401
  min-height: 0;
1358
1402
  }
1359
1403
  .preview-header {
1404
+ display: flex;
1405
+ align-items: center;
1406
+ justify-content: space-between;
1360
1407
  padding: 0.5rem 1rem;
1361
1408
  background: #2d2d2d;
1362
1409
  border-bottom: 1px solid var(--light-border-primary);
@@ -1366,6 +1413,16 @@
1366
1413
  font-size: 0.85rem;
1367
1414
  font-family: "JetBrains Mono", "Fira Code", monospace;
1368
1415
  }
1416
+ :global(.preview-logo) {
1417
+ width: 18px;
1418
+ height: 18px;
1419
+ color: var(--editor-accent, #8bc48b);
1420
+ opacity: 0.6;
1421
+ transition: opacity 0.2s ease;
1422
+ }
1423
+ :global(.preview-logo:hover) {
1424
+ opacity: 1;
1425
+ }
1369
1426
  .preview-content {
1370
1427
  flex: 1;
1371
1428
  padding: 1rem;
@@ -4,9 +4,12 @@ type MarkdownEditor = {
4
4
  $set?(props: Partial<$$ComponentProps>): void;
5
5
  } & {
6
6
  getAvailableAnchors: () => string[];
7
- insertAnchor: (name: any) => void;
7
+ insertAnchor: (name: string) => void;
8
8
  clearDraft: () => void;
9
- getDraftStatus: () => any;
9
+ getDraftStatus: () => {
10
+ hasDraft: boolean;
11
+ storedDraft: StoredDraft | null;
12
+ };
10
13
  };
11
14
  declare const MarkdownEditor: import("svelte").Component<{
12
15
  content?: string;
@@ -14,15 +17,18 @@ declare const MarkdownEditor: import("svelte").Component<{
14
17
  saving?: boolean;
15
18
  readonly?: boolean;
16
19
  draftKey?: any;
17
- onDraftRestored?: Function;
20
+ onDraftRestored?: any;
18
21
  previewTitle?: string;
19
22
  previewDate?: string;
20
- previewTags?: any[];
23
+ previewTags?: any;
21
24
  }, {
22
25
  getAvailableAnchors: () => string[];
23
- insertAnchor: (name: any) => void;
26
+ insertAnchor: (name: string) => void;
24
27
  clearDraft: () => void;
25
- getDraftStatus: () => any;
28
+ getDraftStatus: () => {
29
+ hasDraft: boolean;
30
+ storedDraft: import("./composables/useDraftManager.svelte.js").StoredDraft | null;
31
+ };
26
32
  }, "content">;
27
33
  type $$ComponentProps = {
28
34
  content?: string;
@@ -30,8 +36,8 @@ type $$ComponentProps = {
30
36
  saving?: boolean;
31
37
  readonly?: boolean;
32
38
  draftKey?: any;
33
- onDraftRestored?: Function;
39
+ onDraftRestored?: any;
34
40
  previewTitle?: string;
35
41
  previewDate?: string;
36
- previewTags?: any[];
42
+ previewTags?: any;
37
43
  };
@@ -1,8 +1,35 @@
1
+ /**
2
+ * @typedef {Object} AmbientSoundsState
3
+ * @property {boolean} enabled
4
+ * @property {string} currentSound
5
+ * @property {number} volume
6
+ * @property {boolean} showPanel
7
+ */
8
+ /**
9
+ * @typedef {Object} AmbientSoundsManager
10
+ * @property {AmbientSoundsState} state
11
+ * @property {boolean} enabled
12
+ * @property {string} currentSound
13
+ * @property {number} volume
14
+ * @property {boolean} showPanel
15
+ * @property {() => void} loadSettings
16
+ * @property {() => void} toggle
17
+ * @property {(soundKey: string) => void} play
18
+ * @property {() => void} stop
19
+ * @property {(volume: number) => void} setVolume
20
+ * @property {(soundKey: string) => void} selectSound
21
+ * @property {() => void} togglePanel
22
+ * @property {() => void} closePanel
23
+ * @property {() => void} cleanup
24
+ */
25
+ /**
26
+ * @typedef {keyof typeof soundLibrary} SoundKey
27
+ */
1
28
  /**
2
29
  * Creates an ambient sounds manager with Svelte 5 runes
3
- * @returns {object} Ambient sounds state and controls
30
+ * @returns {AmbientSoundsManager} Ambient sounds state and controls
4
31
  */
5
- export function useAmbientSounds(): object;
32
+ export function useAmbientSounds(): AmbientSoundsManager;
6
33
  export namespace soundLibrary {
7
34
  namespace forest {
8
35
  let name: string;
@@ -51,3 +78,26 @@ export namespace soundLibrary {
51
78
  export { description_4 as description };
52
79
  }
53
80
  }
81
+ export type AmbientSoundsState = {
82
+ enabled: boolean;
83
+ currentSound: string;
84
+ volume: number;
85
+ showPanel: boolean;
86
+ };
87
+ export type AmbientSoundsManager = {
88
+ state: AmbientSoundsState;
89
+ enabled: boolean;
90
+ currentSound: string;
91
+ volume: number;
92
+ showPanel: boolean;
93
+ loadSettings: () => void;
94
+ toggle: () => void;
95
+ play: (soundKey: string) => void;
96
+ stop: () => void;
97
+ setVolume: (volume: number) => void;
98
+ selectSound: (soundKey: string) => void;
99
+ togglePanel: () => void;
100
+ closePanel: () => void;
101
+ cleanup: () => void;
102
+ };
103
+ export type SoundKey = keyof typeof soundLibrary;
@@ -39,18 +39,49 @@ export const soundLibrary = {
39
39
  },
40
40
  };
41
41
 
42
+ /**
43
+ * @typedef {Object} AmbientSoundsState
44
+ * @property {boolean} enabled
45
+ * @property {string} currentSound
46
+ * @property {number} volume
47
+ * @property {boolean} showPanel
48
+ */
49
+
50
+ /**
51
+ * @typedef {Object} AmbientSoundsManager
52
+ * @property {AmbientSoundsState} state
53
+ * @property {boolean} enabled
54
+ * @property {string} currentSound
55
+ * @property {number} volume
56
+ * @property {boolean} showPanel
57
+ * @property {() => void} loadSettings
58
+ * @property {() => void} toggle
59
+ * @property {(soundKey: string) => void} play
60
+ * @property {() => void} stop
61
+ * @property {(volume: number) => void} setVolume
62
+ * @property {(soundKey: string) => void} selectSound
63
+ * @property {() => void} togglePanel
64
+ * @property {() => void} closePanel
65
+ * @property {() => void} cleanup
66
+ */
67
+
68
+ /**
69
+ * @typedef {keyof typeof soundLibrary} SoundKey
70
+ */
71
+
42
72
  /**
43
73
  * Creates an ambient sounds manager with Svelte 5 runes
44
- * @returns {object} Ambient sounds state and controls
74
+ * @returns {AmbientSoundsManager} Ambient sounds state and controls
45
75
  */
46
76
  export function useAmbientSounds() {
47
77
  let state = $state({
48
78
  enabled: false,
49
- currentSound: "forest",
79
+ currentSound: /** @type {string} */ ("forest"),
50
80
  volume: 0.3,
51
81
  showPanel: false,
52
82
  });
53
83
 
84
+ /** @type {HTMLAudioElement | null} */
54
85
  let audioElement = $state(null);
55
86
 
56
87
  function loadSettings() {
@@ -89,8 +120,9 @@ export function useAmbientSounds() {
89
120
  }
90
121
  }
91
122
 
123
+ /** @param {string} soundKey */
92
124
  function play(soundKey) {
93
- const sound = soundLibrary[soundKey];
125
+ const sound = soundLibrary[/** @type {keyof typeof soundLibrary} */ (soundKey)];
94
126
  if (!sound) return;
95
127
 
96
128
  // Stop current sound if playing
@@ -117,7 +149,7 @@ export function useAmbientSounds() {
117
149
  state.currentSound = soundKey;
118
150
  saveSettings();
119
151
  })
120
- .catch((e) => {
152
+ .catch((/** @type {Error} */ e) => {
121
153
  console.warn("Failed to play sound:", e);
122
154
  state.enabled = false;
123
155
  });
@@ -131,6 +163,7 @@ export function useAmbientSounds() {
131
163
  state.enabled = false;
132
164
  }
133
165
 
166
+ /** @param {number} newVolume */
134
167
  function setVolume(newVolume) {
135
168
  state.volume = newVolume;
136
169
  if (audioElement) {
@@ -139,6 +172,7 @@ export function useAmbientSounds() {
139
172
  saveSettings();
140
173
  }
141
174
 
175
+ /** @param {string} soundKey */
142
176
  function selectSound(soundKey) {
143
177
  if (state.enabled) {
144
178
  play(soundKey);
@@ -2,16 +2,86 @@
2
2
  * Command Palette Composable
3
3
  * Manages the command palette (Cmd+K) functionality
4
4
  */
5
+ /**
6
+ * @typedef {Object} PaletteAction
7
+ * @property {string} id
8
+ * @property {string} label
9
+ * @property {string} shortcut
10
+ * @property {() => void} action
11
+ * @property {string} [themeKey]
12
+ * @property {boolean} [isTheme]
13
+ */
14
+ /**
15
+ * @typedef {Object} CommandPaletteState
16
+ * @property {boolean} open
17
+ * @property {string} query
18
+ * @property {number} selectedIndex
19
+ */
20
+ /**
21
+ * @typedef {Object} CommandPaletteOptions
22
+ * @property {() => PaletteAction[]} [getActions] - Function to get available actions
23
+ * @property {() => Record<string, any>} [getThemes] - Function to get available themes
24
+ * @property {() => string} [getCurrentTheme] - Function to get current theme
25
+ */
26
+ /**
27
+ * @typedef {Object} CommandPaletteManager
28
+ * @property {CommandPaletteState} state
29
+ * @property {boolean} isOpen
30
+ * @property {string} query
31
+ * @property {number} selectedIndex
32
+ * @property {() => PaletteAction[]} getAllCommands
33
+ * @property {() => PaletteAction[]} getFilteredCommands
34
+ * @property {() => void} open
35
+ * @property {() => void} close
36
+ * @property {() => void} toggle
37
+ * @property {(direction: 'up' | 'down') => void} navigate
38
+ * @property {(index: number) => PaletteAction | undefined} execute
39
+ * @property {(query: string) => void} setQuery
40
+ */
5
41
  /**
6
42
  * Creates a command palette manager with Svelte 5 runes
7
- * @param {object} options - Configuration options
8
- * @param {Function} options.getActions - Function to get available actions
9
- * @param {Function} options.getThemes - Function to get available themes
10
- * @param {Function} options.getCurrentTheme - Function to get current theme
11
- * @returns {object} Command palette state and controls
43
+ * @param {CommandPaletteOptions} options - Configuration options
44
+ * @returns {CommandPaletteManager} Command palette state and controls
12
45
  */
13
- export function useCommandPalette(options?: {
14
- getActions: Function;
15
- getThemes: Function;
16
- getCurrentTheme: Function;
17
- }): object;
46
+ export function useCommandPalette(options?: CommandPaletteOptions): CommandPaletteManager;
47
+ export type PaletteAction = {
48
+ id: string;
49
+ label: string;
50
+ shortcut: string;
51
+ action: () => void;
52
+ themeKey?: string | undefined;
53
+ isTheme?: boolean | undefined;
54
+ };
55
+ export type CommandPaletteState = {
56
+ open: boolean;
57
+ query: string;
58
+ selectedIndex: number;
59
+ };
60
+ export type CommandPaletteOptions = {
61
+ /**
62
+ * - Function to get available actions
63
+ */
64
+ getActions?: (() => PaletteAction[]) | undefined;
65
+ /**
66
+ * - Function to get available themes
67
+ */
68
+ getThemes?: (() => Record<string, any>) | undefined;
69
+ /**
70
+ * - Function to get current theme
71
+ */
72
+ getCurrentTheme?: (() => string) | undefined;
73
+ };
74
+ export type CommandPaletteManager = {
75
+ state: CommandPaletteState;
76
+ isOpen: boolean;
77
+ query: string;
78
+ selectedIndex: number;
79
+ getAllCommands: () => PaletteAction[];
80
+ getFilteredCommands: () => PaletteAction[];
81
+ open: () => void;
82
+ close: () => void;
83
+ toggle: () => void;
84
+ navigate: (direction: "up" | "down") => void;
85
+ execute: (index: number) => PaletteAction | undefined;
86
+ setQuery: (query: string) => void;
87
+ };