@openviking/react 0.1.5 → 0.1.6

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 (101) hide show
  1. package/README.md +13 -11
  2. package/dist/37~0.js +27 -0
  3. package/dist/3c~0.js +14 -8
  4. package/dist/3~0.js +1 -1
  5. package/dist/a~0.js +2 -2
  6. package/dist/ck~0.js +4 -0
  7. package/dist/client/paths.d.ts +4 -0
  8. package/dist/components/agent-connect/style.css +48 -4
  9. package/dist/components/directory-tour/style.css +46 -2
  10. package/dist/components/experience-dashboard/dashboard-tabs.d.ts +11 -0
  11. package/dist/components/experience-dashboard/experience-dashboard.d.ts +2 -0
  12. package/dist/components/experience-dashboard/experience-impact.d.ts +11 -0
  13. package/dist/components/experience-dashboard/experience-list.d.ts +9 -0
  14. package/dist/components/experience-dashboard/experience-pagination.d.ts +8 -0
  15. package/dist/components/experience-dashboard/experience-tests.d.ts +6 -0
  16. package/dist/components/experience-dashboard/experience-trajectories.d.ts +6 -0
  17. package/dist/components/experience-dashboard/index.d.ts +2 -0
  18. package/dist/components/experience-dashboard/labels.d.ts +2 -0
  19. package/dist/components/experience-dashboard/style.css +699 -0
  20. package/dist/components/experience-dashboard/style.js +0 -0
  21. package/dist/components/experience-dashboard/types.d.ts +106 -0
  22. package/dist/components/experience-dashboard.js +1 -0
  23. package/dist/components/file-browser-entries/style.css +144 -12
  24. package/dist/components/file-preview/style.css +48 -4
  25. package/dist/components/file-tree/style.css +63 -4
  26. package/dist/components/scope-switcher/style.css +48 -4
  27. package/dist/components/scope-switcher.js +1 -1
  28. package/dist/components/style.css +2168 -84
  29. package/dist/components/tui/style.css +122 -36
  30. package/dist/components/viking-bot/style.css +159 -12
  31. package/dist/components/wiki-dashboard/index.d.ts +2 -0
  32. package/dist/components/wiki-dashboard/labels.d.ts +2 -0
  33. package/dist/components/wiki-dashboard/style.css +741 -0
  34. package/dist/components/wiki-dashboard/style.js +0 -0
  35. package/dist/components/wiki-dashboard/types.d.ts +92 -0
  36. package/dist/components/wiki-dashboard/wiki-action-button.d.ts +9 -0
  37. package/dist/components/wiki-dashboard/wiki-conflict-card.d.ts +15 -0
  38. package/dist/components/wiki-dashboard/wiki-dashboard.d.ts +2 -0
  39. package/dist/components/wiki-dashboard/wiki-file-icon.d.ts +3 -0
  40. package/dist/components/wiki-dashboard/wiki-severity-filter.d.ts +8 -0
  41. package/dist/components/wiki-dashboard.js +1 -0
  42. package/dist/c~0.js +1 -1
  43. package/dist/fi~0.js +354 -0
  44. package/dist/fj~0.js +503 -0
  45. package/dist/f~0.js +661 -315
  46. package/dist/icons/index.d.ts +28 -0
  47. package/dist/icons.js +1 -1
  48. package/dist/index.d.ts +4 -0
  49. package/dist/index.js +3 -1
  50. package/dist/k~0.js +277 -1
  51. package/dist/lk~0.js +52 -13
  52. package/dist/l~0.js +1021 -118
  53. package/dist/modules/console/index.d.ts +1 -2
  54. package/dist/modules/console/style.css +377 -56
  55. package/dist/modules/console.js +1 -1
  56. package/dist/modules/file-browser/dashboard-data.d.ts +9 -0
  57. package/dist/modules/file-browser/dashboard-indicator.d.ts +8 -0
  58. package/dist/modules/file-browser/dashboard-labels.d.ts +9 -0
  59. package/dist/modules/file-browser/dashboard-model.d.ts +14 -0
  60. package/dist/modules/file-browser/dashboard-resource.d.ts +12 -0
  61. package/dist/modules/file-browser/experience-dashboard-pane.d.ts +8 -0
  62. package/dist/modules/file-browser/experience-data.d.ts +31 -0
  63. package/dist/modules/file-browser/file-browser-labels.d.ts +1 -1
  64. package/dist/modules/file-browser/file-browser-tree-pane.d.ts +3 -2
  65. package/dist/modules/file-browser/file-browser.d.ts +1 -1
  66. package/dist/modules/file-browser/fs-directories.d.ts +8 -0
  67. package/dist/modules/file-browser/fs-lines.d.ts +36 -0
  68. package/dist/modules/file-browser/fs-memory-fields.d.ts +65 -0
  69. package/dist/modules/file-browser/fs-model.d.ts +12 -2
  70. package/dist/modules/file-browser/fs-read-file.d.ts +14 -0
  71. package/dist/modules/file-browser/fs-selection.d.ts +4 -0
  72. package/dist/modules/file-browser/index.d.ts +2 -2
  73. package/dist/modules/file-browser/style.css +1768 -24
  74. package/dist/modules/file-browser/types.d.ts +62 -0
  75. package/dist/modules/file-browser/use-browser-dashboards.d.ts +31 -0
  76. package/dist/modules/file-browser/use-dashboard-query.d.ts +9 -0
  77. package/dist/modules/file-browser/use-experience-dashboard.d.ts +27 -0
  78. package/dist/modules/file-browser/use-file-browser.d.ts +2 -45
  79. package/dist/modules/file-browser/use-fs-tree.d.ts +2 -0
  80. package/dist/modules/file-browser/use-wiki-dashboard.d.ts +17 -0
  81. package/dist/modules/file-browser/wiki-conflicts.d.ts +12 -0
  82. package/dist/modules/file-browser/wiki-dashboard-pane.d.ts +14 -0
  83. package/dist/modules/file-browser.js +1 -1
  84. package/dist/modules/scope-cascade/controller.d.ts +2 -2
  85. package/dist/modules/scope-cascade/index.d.ts +1 -2
  86. package/dist/modules/scope-cascade/scope-cascade.d.ts +1 -1
  87. package/dist/modules/scope-cascade/style.css +48 -4
  88. package/dist/modules/scope-cascade/types.d.ts +8 -3
  89. package/dist/modules/scope-cascade/use-scope-cascade.d.ts +2 -2
  90. package/dist/modules/scope-cascade.js +1 -1
  91. package/dist/modules/style.css +2344 -191
  92. package/dist/n~0.js +13 -129
  93. package/dist/rt~0.js +152 -0
  94. package/dist/r~0.js +17 -38
  95. package/dist/shared/components/popover/popover.d.ts +1 -1
  96. package/dist/shared/components/popover/types.d.ts +7 -1
  97. package/dist/shared/dashboard/dashboard-state.d.ts +12 -0
  98. package/dist/static/image/dashboard-background.png +0 -0
  99. package/dist/style.css +8897 -4616
  100. package/dist/viking-bot-composer~0.js +1 -1
  101. package/package.json +12 -2
package/dist/l~0.js CHANGED
@@ -1,14 +1,16 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
2
+ import { createElement, useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
3
3
  import { FilePreview as file_preview_FilePreview } from "./3~0.js";
4
- import { Tooltip } from "./r~0.js";
5
- import { getFileTypeColor, resolveFileTypeIconKey, FolderIcon, getFileTypeIcon } from "./k~0.js";
4
+ import { Tooltip } from "./rt~0.js";
5
+ import { getFileTypeColor, getFileTypeIcon, WikiBookIcon, ExperienceBoardIcon, WikiConflictBoardIcon, FolderIcon, WikiBoardIcon, resolveFileTypeIconKey } from "./k~0.js";
6
6
  import { FileBrowserEntries } from "./2~0.js";
7
7
  import { FileTree as file_tree_FileTree } from "./n~0.js";
8
8
  import { resolveLocale } from "./nx~0.js";
9
9
  import { OV_PATHS } from "./ck~0.js";
10
+ import { isOvClientError, useOvSignal, useOvClient } from "./ae~0.js";
11
+ import { ExperienceDashboard } from "./f~0.js";
12
+ import { WikiDashboard } from "./fj~0.js";
10
13
  import { useLocale, LocaleProvider } from "./28~0.js";
11
- import { useOvClient } from "./ae~0.js";
12
14
  function clampResizableWidth(width, min, max) {
13
15
  const rounded = Math.round(width);
14
16
  const lower = Math.max(min, rounded);
@@ -238,6 +240,26 @@ function resolveTreeMaxWidth(containerWidth) {
238
240
  if (!containerWidth) return TREE_MAX_WIDTH;
239
241
  return Math.max(TREE_MIN_WIDTH, Math.min(TREE_MAX_WIDTH, containerWidth - DETAIL_MIN_WIDTH - RESIZE_FOOTPRINT));
240
242
  }
243
+ const en = {
244
+ experienceTitle: 'Experience dashboard',
245
+ wikiTitle: 'Dashboard',
246
+ openExperience: 'Open experience dashboard',
247
+ openWiki: 'Open Wiki dashboard',
248
+ wikiConflicts: 'View pending Wiki conflicts',
249
+ failed: 'Could not load dashboard information'
250
+ };
251
+ const zh = {
252
+ experienceTitle: '经验看板',
253
+ wikiTitle: '看板',
254
+ openExperience: '打开经验看板',
255
+ openWiki: '打开 Wiki 看板',
256
+ wikiConflicts: '查看 Wiki 待确认冲突',
257
+ failed: '看板信息加载失败'
258
+ };
259
+ const dashboard_labels_dashboardLabels = (locale, labels)=>({
260
+ ...'zh-CN' === locale ? zh : en,
261
+ ...labels
262
+ });
241
263
  const file_browser_icons_SIZE = 16;
242
264
  const strokeProps = {
243
265
  width: file_browser_icons_SIZE,
@@ -304,6 +326,25 @@ function FileBrowserToolbar({ searchId, query, refreshing, canRefresh, placehold
304
326
  ]
305
327
  });
306
328
  }
329
+ function DashboardIndicator({ dashboard, locale, labels, onOpen }) {
330
+ const text = dashboard_labels_dashboardLabels(locale, labels);
331
+ const label = 'experience' === dashboard.kind ? text.openExperience : dashboard.pendingConflicts ? text.wikiConflicts : text.openWiki;
332
+ const Icon = 'experience' === dashboard.kind ? ExperienceBoardIcon : dashboard.pendingConflicts ? WikiConflictBoardIcon : WikiBoardIcon;
333
+ return /*#__PURE__*/ jsx("button", {
334
+ type: "button",
335
+ className: `ov-file-browser__dashboard${dashboard.pendingConflicts ? ' ov-file-browser__dashboard--pending' : ''}`,
336
+ "aria-label": label,
337
+ title: label,
338
+ onClick: (event)=>{
339
+ event.stopPropagation();
340
+ onOpen(dashboard.uri);
341
+ },
342
+ children: /*#__PURE__*/ jsx(Icon, {
343
+ width: 12,
344
+ height: 12
345
+ })
346
+ });
347
+ }
307
348
  function fileTypeIcon(name) {
308
349
  const Icon = getFileTypeIcon(name);
309
350
  const key = resolveFileTypeIconKey(name);
@@ -320,7 +361,7 @@ function directoryIcon() {
320
361
  className: "ov-file-tree__file-type-icon"
321
362
  });
322
363
  }
323
- function FileBrowserTreePane({ entries, locale, searchId, browser, treeLabels, searchPlaceholder, refreshLabel, searchLoading, searchEmpty, searchErrorLabel, retryLabel }) {
364
+ function FileBrowserTreePane({ entries, locale, dashboardLabels, searchId, browser, treeLabels, searchPlaceholder, refreshLabel, searchLoading, searchEmpty, searchErrorLabel, retryLabel }) {
324
365
  return /*#__PURE__*/ jsxs(Fragment, {
325
366
  children: [
326
367
  /*#__PURE__*/ jsx(FileBrowserEntries, {
@@ -338,44 +379,77 @@ function FileBrowserTreePane({ entries, locale, searchId, browser, treeLabels, s
338
379
  onQueryChange: browser.actions.setQuery,
339
380
  onRefresh: ()=>void browser.actions.refresh()
340
381
  }),
341
- /*#__PURE__*/ jsx("div", {
382
+ /*#__PURE__*/ jsxs("div", {
342
383
  className: "ov-file-browser__tree-scroll",
343
- children: /*#__PURE__*/ jsx(file_tree_FileTree, {
344
- locale: locale,
345
- rows: browser.rows,
346
- selectedId: browser.selectedId,
347
- isReady: browser.isReady,
348
- labels: treeLabels,
349
- emptyFallback: browser.searching ? browser.searchError ? /*#__PURE__*/ jsxs("div", {
384
+ children: [
385
+ browser.dashboardError ? /*#__PURE__*/ jsxs("div", {
350
386
  className: "ov-file-browser__hint ov-file-browser__hint--error",
387
+ "aria-live": "polite",
351
388
  children: [
352
- /*#__PURE__*/ jsx("p", {
353
- className: "ov-file-browser__failure-title",
354
- children: searchErrorLabel
355
- }),
356
- /*#__PURE__*/ jsx("p", {
357
- className: "ov-file-browser__failure-detail",
358
- children: browser.searchError.message
389
+ /*#__PURE__*/ jsxs("p", {
390
+ children: [
391
+ dashboard_labels_dashboardLabels(locale, dashboardLabels).failed,
392
+ ":",
393
+ ' ',
394
+ browser.dashboardError.message
395
+ ]
359
396
  }),
360
397
  /*#__PURE__*/ jsx("button", {
361
398
  type: "button",
362
- className: "ov-file-browser__retry",
363
399
  onClick: ()=>void browser.actions.refresh(),
364
400
  children: retryLabel
365
401
  })
366
402
  ]
367
- }) : /*#__PURE__*/ jsx("p", {
368
- className: "ov-file-browser__hint",
369
- children: browser.searchLoading ? searchLoading : searchEmpty
370
- }) : void 0,
371
- renderRowIcon: (row)=>fileTypeIcon(row.name),
372
- operations: {
373
- select: browser.actions.select,
374
- toggleExpand: browser.actions.toggleExpand,
375
- loadMore: browser.actions.loadMore,
376
- retry: browser.actions.retry
377
- }
378
- })
403
+ }) : null,
404
+ /*#__PURE__*/ jsx(file_tree_FileTree, {
405
+ locale: locale,
406
+ rows: browser.rows,
407
+ selectedId: browser.selectedId,
408
+ isReady: browser.isReady,
409
+ labels: treeLabels,
410
+ emptyFallback: browser.searching ? browser.searchError ? /*#__PURE__*/ jsxs("div", {
411
+ className: "ov-file-browser__hint ov-file-browser__hint--error",
412
+ children: [
413
+ /*#__PURE__*/ jsx("p", {
414
+ className: "ov-file-browser__failure-title",
415
+ children: searchErrorLabel
416
+ }),
417
+ /*#__PURE__*/ jsx("p", {
418
+ className: "ov-file-browser__failure-detail",
419
+ children: browser.searchError.message
420
+ }),
421
+ /*#__PURE__*/ jsx("button", {
422
+ type: "button",
423
+ className: "ov-file-browser__retry",
424
+ onClick: ()=>void browser.actions.refresh(),
425
+ children: retryLabel
426
+ })
427
+ ]
428
+ }) : /*#__PURE__*/ jsx("p", {
429
+ className: "ov-file-browser__hint",
430
+ children: browser.searchLoading ? searchLoading : searchEmpty
431
+ }) : void 0,
432
+ renderRowIcon: (row)=>browser.dashboards.get(row.uri)?.isRoot ? /*#__PURE__*/ jsx(WikiBookIcon, {
433
+ width: 14,
434
+ height: 14
435
+ }) : fileTypeIcon(row.name),
436
+ renderDashboard: (row)=>{
437
+ const dashboard = browser.dashboards.get(row.uri);
438
+ return dashboard ? /*#__PURE__*/ jsx(DashboardIndicator, {
439
+ dashboard: dashboard,
440
+ locale: locale,
441
+ labels: dashboardLabels,
442
+ onOpen: browser.actions.openDashboard
443
+ }) : null;
444
+ },
445
+ operations: {
446
+ select: browser.actions.select,
447
+ toggleExpand: browser.actions.toggleExpand,
448
+ loadMore: browser.actions.loadMore,
449
+ retry: browser.actions.retry
450
+ }
451
+ })
452
+ ]
379
453
  })
380
454
  ]
381
455
  });
@@ -476,6 +550,11 @@ function useContainerWidth() {
476
550
  width
477
551
  };
478
552
  }
553
+ function selectedRowId(uri, results) {
554
+ if (!uri || null === results) return uri;
555
+ const index = results.findIndex((entry)=>entry.uri === uri);
556
+ return index < 0 ? uri : `${uri}#${index}`;
557
+ }
479
558
  const emptyDir = ()=>({
480
559
  entries: [],
481
560
  hasMore: false,
@@ -503,6 +582,7 @@ function nameFromUri(uri) {
503
582
  function toEntryRow(entry, depth, expanded, index, busy) {
504
583
  const isDir = true === entry.isDir;
505
584
  return {
585
+ tags: entry.tags,
506
586
  kind: 'entry',
507
587
  id: void 0 === index ? entry.uri : `${entry.uri}#${index}`,
508
588
  depth,
@@ -513,7 +593,7 @@ function toEntryRow(entry, depth, expanded, index, busy) {
513
593
  busy: busy || void 0
514
594
  };
515
595
  }
516
- function toFileTab(entry, content, maxLines, icon, openParent) {
596
+ function toFileTab(entry, content, maxLines, icon, openParent, truncated = content.split('\n').length >= maxLines) {
517
597
  const name = nameFromUri(entry.uri);
518
598
  return {
519
599
  kind: 'file',
@@ -527,7 +607,7 @@ function toFileTab(entry, content, maxLines, icon, openParent) {
527
607
  content,
528
608
  fileName: name,
529
609
  path: entry.uri,
530
- truncated: content.split('\n').length >= maxLines
610
+ truncated
531
611
  }
532
612
  };
533
613
  }
@@ -612,6 +692,39 @@ function isAbort(error) {
612
692
  function toError(cause) {
613
693
  return cause instanceof Error ? cause : new Error(String(cause));
614
694
  }
695
+ async function readDirectory(client, uri, options) {
696
+ const read = (path)=>client.request({
697
+ method: 'GET',
698
+ path,
699
+ query: {
700
+ uri
701
+ },
702
+ options
703
+ }).then((value)=>({
704
+ ok: true,
705
+ text: 'string' == typeof value ? value : ''
706
+ })).catch((cause)=>({
707
+ ok: false,
708
+ cause
709
+ }));
710
+ const [abstractResult, overviewResult] = await Promise.all([
711
+ read(OV_PATHS.contentAbstract),
712
+ read(OV_PATHS.contentOverview)
713
+ ]);
714
+ const aborted = [
715
+ abstractResult,
716
+ overviewResult
717
+ ].find((result)=>!result.ok && isAbort(result.cause));
718
+ if (aborted && !aborted.ok) throw aborted.cause;
719
+ if (!abstractResult.ok && !overviewResult.ok) throw abstractResult.cause;
720
+ const reason = (result)=>result.ok ? void 0 : result.cause instanceof Error ? result.cause.message : String(result.cause);
721
+ return {
722
+ abstract: abstractResult.ok ? abstractResult.text : '',
723
+ overview: overviewResult.ok ? overviewResult.text : '',
724
+ abstractError: reason(abstractResult),
725
+ overviewError: reason(overviewResult)
726
+ };
727
+ }
615
728
  const BINARY_EXTENSIONS = new Set([
616
729
  'png',
617
730
  'jpg',
@@ -695,38 +808,152 @@ function toBinaryTab(uri, name, file, icon, openParent) {
695
808
  }
696
809
  };
697
810
  }
698
- async function readDirectory(client, uri, options) {
699
- const read = (path)=>client.request({
811
+ const LINE_BREAK_CODES = new Set([
812
+ 0x0a,
813
+ 0x0b,
814
+ 0x0c,
815
+ 0x0d,
816
+ 0x1c,
817
+ 0x1d,
818
+ 0x1e,
819
+ 0x85,
820
+ 0x2028,
821
+ 0x2029
822
+ ]);
823
+ function splitLines(text) {
824
+ if (!text) return [];
825
+ const lines = [];
826
+ let start = 0;
827
+ for(let index = 0; index < text.length; index++){
828
+ if (!LINE_BREAK_CODES.has(text.charCodeAt(index))) continue;
829
+ const paired = 0x0d === text.charCodeAt(index) && 0x0a === text.charCodeAt(index + 1);
830
+ const end = index + (paired ? 2 : 1);
831
+ lines.push(text.slice(start, end));
832
+ start = end;
833
+ index = end - 1;
834
+ }
835
+ if (start < text.length) lines.push(text.slice(start));
836
+ return lines;
837
+ }
838
+ function joinLines(lines) {
839
+ return lines.join('');
840
+ }
841
+ const PY_WHITESPACE_CODES = [
842
+ 0x09,
843
+ 0x0a,
844
+ 0x0b,
845
+ 0x0c,
846
+ 0x0d,
847
+ 0x1c,
848
+ 0x1d,
849
+ 0x1e,
850
+ 0x1f,
851
+ 0x20,
852
+ 0x85,
853
+ 0xa0,
854
+ 0x1680,
855
+ 0x2000,
856
+ 0x2001,
857
+ 0x2002,
858
+ 0x2003,
859
+ 0x2004,
860
+ 0x2005,
861
+ 0x2006,
862
+ 0x2007,
863
+ 0x2008,
864
+ 0x2009,
865
+ 0x200a,
866
+ 0x2028,
867
+ 0x2029,
868
+ 0x202f,
869
+ 0x205f,
870
+ 0x3000
871
+ ];
872
+ const PY_WHITESPACE = String.fromCharCode(...PY_WHITESPACE_CODES);
873
+ const PY_WORD = '\\p{L}\\p{N}_';
874
+ const MEMORY_FIELDS_COMMENT = new RegExp(`<!--[${PY_WHITESPACE}]*MEMORY_FIELDS(?![${PY_WORD}])[\\s\\S]*?[${PY_WHITESPACE}]*-->`, 'gu');
875
+ function isPythonBlank(text) {
876
+ for (const char of text)if (!PY_WHITESPACE.includes(char)) return false;
877
+ return true;
878
+ }
879
+ function trimPythonEnd(text) {
880
+ let end = text.length;
881
+ while(end > 0 && PY_WHITESPACE.includes(text[end - 1] ?? ''))end--;
882
+ return text.slice(0, end);
883
+ }
884
+ function uriParts(uri) {
885
+ const [beforeQuery = ''] = uri.split('?');
886
+ const path = beforeQuery.replace(/\/+$/, '');
887
+ if (!path.startsWith('viking://')) return [];
888
+ return path.slice(9).split('/').filter(Boolean);
889
+ }
890
+ function isMemoryUri(uri) {
891
+ const parts = uriParts(uri);
892
+ if (parts.length < 3) return false;
893
+ if ('user' !== parts[0] && '~' !== parts[0]) return false;
894
+ const scoped = '~' === parts[0] ? [
895
+ 'user',
896
+ '~',
897
+ ...parts.slice(1)
898
+ ] : parts;
899
+ if ('memories' === scoped[2]) return true;
900
+ return scoped.length >= 5 && 'peers' === scoped[2] && 'memories' === scoped[4];
901
+ }
902
+ function stripMemoryFieldsTrailer(text) {
903
+ let visible = text;
904
+ for(;;){
905
+ let start = -1;
906
+ MEMORY_FIELDS_COMMENT.lastIndex = 0;
907
+ let match;
908
+ while(null !== (match = MEMORY_FIELDS_COMMENT.exec(visible))){
909
+ const after = visible.slice(match.index + match[0].length);
910
+ if (isPythonBlank(after)) start = match.index;
911
+ }
912
+ if (start < 0) return visible;
913
+ visible = trimPythonEnd(visible.slice(0, start));
914
+ }
915
+ }
916
+ const MAX_LINES = 2000;
917
+ async function readFile(client, uri, options, openParent) {
918
+ const name = nameFromUri(uri);
919
+ const icon = fileTypeIcon(name);
920
+ if (isBinaryFile(name)) {
921
+ const blob = await client.request({
700
922
  method: 'GET',
701
- path,
923
+ path: OV_PATHS.contentDownload,
702
924
  query: {
703
925
  uri
704
926
  },
927
+ responseType: 'blob',
705
928
  options
706
- }).then((value)=>({
707
- ok: true,
708
- text: 'string' == typeof value ? value : ''
709
- })).catch((cause)=>({
710
- ok: false,
711
- cause
712
- }));
713
- const [abstractResult, overviewResult] = await Promise.all([
714
- read(OV_PATHS.contentAbstract),
715
- read(OV_PATHS.contentOverview)
716
- ]);
717
- const aborted = [
718
- abstractResult,
719
- overviewResult
720
- ].find((result)=>!result.ok && isAbort(result.cause));
721
- if (aborted && !aborted.ok) throw aborted.cause;
722
- if (!abstractResult.ok && !overviewResult.ok) throw abstractResult.cause;
723
- const reason = (result)=>result.ok ? void 0 : result.cause instanceof Error ? result.cause.message : String(result.cause);
724
- return {
725
- abstract: abstractResult.ok ? abstractResult.text : '',
726
- overview: overviewResult.ok ? overviewResult.text : '',
727
- abstractError: reason(abstractResult),
728
- overviewError: reason(overviewResult)
729
- };
929
+ });
930
+ const typed = new Blob([
931
+ blob
932
+ ], {
933
+ type: mimeTypeOf(name)
934
+ });
935
+ return toBinaryTab(uri, name, typed, icon, openParent);
936
+ }
937
+ const read = (limit)=>client.request({
938
+ method: 'GET',
939
+ path: OV_PATHS.contentRead,
940
+ query: {
941
+ uri,
942
+ limit
943
+ },
944
+ options
945
+ }).then((value)=>'string' == typeof value ? value : '');
946
+ const first = await read(MAX_LINES);
947
+ if (!isMemoryUri(uri)) return toFileTab({
948
+ uri
949
+ }, first, MAX_LINES, icon, openParent, splitLines(first).length >= MAX_LINES);
950
+ const full = splitLines(first).length < MAX_LINES ? first : await read(-1);
951
+ const lines = splitLines(stripMemoryFieldsTrailer(full));
952
+ const truncated = lines.length > MAX_LINES;
953
+ const visible = joinLines(truncated ? lines.slice(0, MAX_LINES) : lines);
954
+ return toFileTab({
955
+ uri
956
+ }, visible, MAX_LINES, icon, openParent, truncated);
730
957
  }
731
958
  async function resolveIsDirectory(client, uri, options) {
732
959
  try {
@@ -748,7 +975,6 @@ function abortError() {
748
975
  error.name = 'AbortError';
749
976
  return error;
750
977
  }
751
- const MAX_LINES = 2000;
752
978
  function useFsPreview({ client, uri, resourceType = 'file', labels = resolveSummaryLabels('en-US'), formatCharacterCount = resolveLabels(void 0, 'en-US').formatCharacterCount, openParent, generation = 0, requestOptions }) {
753
979
  const [directory, setDirectory] = useState(null);
754
980
  const [tabs, setTabs] = useState(null);
@@ -867,39 +1093,6 @@ function loadingTab(uri, isDirectory) {
867
1093
  }
868
1094
  };
869
1095
  }
870
- async function readFile(client, uri, options, openParent) {
871
- const name = nameFromUri(uri);
872
- const icon = fileTypeIcon(name);
873
- if (isBinaryFile(name)) {
874
- const blob = await client.request({
875
- method: 'GET',
876
- path: OV_PATHS.contentDownload,
877
- query: {
878
- uri
879
- },
880
- responseType: 'blob',
881
- options
882
- });
883
- const typed = new Blob([
884
- blob
885
- ], {
886
- type: mimeTypeOf(name)
887
- });
888
- return toBinaryTab(uri, name, typed, icon, openParent);
889
- }
890
- const content = await client.request({
891
- method: 'GET',
892
- path: OV_PATHS.contentRead,
893
- query: {
894
- uri,
895
- limit: MAX_LINES
896
- },
897
- options
898
- });
899
- return toFileTab({
900
- uri
901
- }, 'string' == typeof content ? content : '', MAX_LINES, icon, openParent);
902
- }
903
1096
  function mergeFindResult(result) {
904
1097
  const seen = new Set();
905
1098
  const entries = [];
@@ -971,6 +1164,659 @@ function useFsSearch({ client, query, targetUri, generation = 0, requestOptions
971
1164
  error
972
1165
  };
973
1166
  }
1167
+ const isHomeUri = (uri)=>'viking://~' === uri || uri.startsWith('viking://~/');
1168
+ async function readDashboardStat(client, uri, options) {
1169
+ const stat = await client.request({
1170
+ method: 'GET',
1171
+ path: OV_PATHS.fsStat,
1172
+ query: {
1173
+ uri
1174
+ },
1175
+ options
1176
+ });
1177
+ const canonical = stat?.uri ?? uri;
1178
+ if ('boolean' != typeof stat?.isDir || 'string' != typeof canonical || !canonical || isHomeUri(canonical)) throw new TypeError('Invalid canonical resource stat');
1179
+ return {
1180
+ uri: canonical,
1181
+ isDir: stat.isDir
1182
+ };
1183
+ }
1184
+ async function resolveDashboardUri(client, uri, options) {
1185
+ return isHomeUri(uri) ? (await readDashboardStat(client, uri, options)).uri : uri;
1186
+ }
1187
+ async function readDashboardHint(client, uri, options) {
1188
+ const result = await client.request({
1189
+ method: 'GET',
1190
+ path: OV_PATHS.fsAttrs,
1191
+ query: {
1192
+ uri
1193
+ },
1194
+ options
1195
+ });
1196
+ return {
1197
+ uri: await resolveDashboardUri(client, result?.uri ?? uri, options),
1198
+ tags: result?.attrs?.tags ?? []
1199
+ };
1200
+ }
1201
+ const EXPERIENCE_PAGE_SIZE = 20;
1202
+ const isExperienceDirectory = (uri)=>/^viking:\/\/(?:user\/[^/]+|~)\/memories\/experiences\/?$/.test(uri);
1203
+ const isExperienceFile = (uri)=>/^viking:\/\/(?:user\/[^/]+|~)\/memories\/experiences\/[^/]+\.md$/i.test(uri);
1204
+ function experienceDateRange(days, now = new Date()) {
1205
+ if (!days) return {};
1206
+ const start = new Date(now);
1207
+ start.setUTCDate(start.getUTCDate() - days + 1);
1208
+ return {
1209
+ start_date: start.toISOString().slice(0, 10),
1210
+ end_date: now.toISOString().slice(0, 10)
1211
+ };
1212
+ }
1213
+ async function readExperiences(client, uri, page, options) {
1214
+ uri = await resolveDashboardUri(client, uri, options);
1215
+ const result = await client.requestEnvelope({
1216
+ method: 'GET',
1217
+ path: OV_PATHS.fsLs,
1218
+ query: {
1219
+ uri,
1220
+ output: 'original',
1221
+ limit: EXPERIENCE_PAGE_SIZE,
1222
+ offset: page * EXPERIENCE_PAGE_SIZE,
1223
+ sort_by: 'mtime',
1224
+ sort_order: 'desc'
1225
+ },
1226
+ options
1227
+ });
1228
+ if (!Array.isArray(result.result)) throw new TypeError('Invalid Experience listing');
1229
+ const prefix = `${uri.replace(/\/+$/, '')}/`;
1230
+ return {
1231
+ items: result.result.filter((item)=>!item.isDir && item.uri.startsWith(prefix) && isExperienceFile(item.uri)),
1232
+ hasMore: result.hasMore
1233
+ };
1234
+ }
1235
+ async function readExperienceSources(client, uri, options) {
1236
+ uri = await resolveDashboardUri(client, uri, options);
1237
+ const result = await client.request({
1238
+ method: 'GET',
1239
+ path: OV_PATHS.fsAttrs,
1240
+ query: {
1241
+ uri
1242
+ },
1243
+ options
1244
+ });
1245
+ const links = result?.attrs?.memory?.links;
1246
+ if (void 0 !== links && !Array.isArray(links)) throw new TypeError('Invalid Experience links');
1247
+ const prefix = uri.slice(0, uri.indexOf('/memories/experiences/')) + '/memories/trajectories/';
1248
+ const seen = new Set();
1249
+ return (links ?? []).flatMap((link)=>{
1250
+ if ('derived_from' !== link.link_type || 'string' != typeof link.to_uri || !link.to_uri.startsWith(prefix) || seen.has(link.to_uri)) return [];
1251
+ seen.add(link.to_uri);
1252
+ return [
1253
+ {
1254
+ uri: link.to_uri,
1255
+ name: link.to_uri.split('/').pop() ?? link.to_uri,
1256
+ description: link.description ?? link.match_text
1257
+ }
1258
+ ];
1259
+ });
1260
+ }
1261
+ const dashboardKey = (uri)=>`dashboard:${uri}`;
1262
+ const within = (uri, root)=>uri === root || uri.startsWith(`${root.replace(/\/+$/, '')}/`);
1263
+ function resolveDashboard(uri, rows, targets) {
1264
+ if (isExperienceDirectory(uri) || isExperienceFile(uri)) return {
1265
+ kind: 'experience',
1266
+ uri
1267
+ };
1268
+ const entries = rows.filter((row)=>'entry' === row.kind);
1269
+ const row = entries.find((item)=>item.uri === uri);
1270
+ const hasWiki = entries.some((item)=>item.tags?.includes('compile_target=true') && within(uri, item.uri));
1271
+ const owner = targets.filter((item)=>within(uri, item.to)).sort((a, b)=>b.to.length - a.to.length)[0];
1272
+ if (!hasWiki && !owner) return;
1273
+ const tagged = row?.tags?.find((tag)=>tag.startsWith('has_conflict='));
1274
+ return {
1275
+ kind: 'wiki',
1276
+ uri,
1277
+ isRoot: Boolean(row?.tags?.includes('compile_target=true') || owner?.to === uri),
1278
+ pendingConflicts: tagged ? 'has_conflict=true' === tagged : owner?.to === uri && void 0 !== owner.conflicts_num ? owner.conflicts_num > 0 : void 0
1279
+ };
1280
+ }
1281
+ async function readCompileTargets(client, rootUri, options) {
1282
+ rootUri = await resolveDashboardUri(client, rootUri, options);
1283
+ const targets = [];
1284
+ let cursor;
1285
+ const seen = new Set();
1286
+ do {
1287
+ const result = await client.request({
1288
+ method: 'GET',
1289
+ path: OV_PATHS.compileConfigs,
1290
+ query: {
1291
+ limit: 100,
1292
+ cursor
1293
+ },
1294
+ options
1295
+ });
1296
+ if (!Array.isArray(result?.targets)) throw new TypeError('Invalid Compile target registry');
1297
+ targets.push(...result.targets.filter((item)=>'string' == typeof item.to && 'memory' !== item.compile_type && false !== item.initialized && (within(item.to, rootUri) || within(rootUri, item.to))));
1298
+ cursor = result.next_cursor ?? void 0;
1299
+ if (cursor && seen.has(cursor)) throw new TypeError('Compile target cursor did not advance');
1300
+ if (cursor) seen.add(cursor);
1301
+ }while (cursor)
1302
+ return targets;
1303
+ }
1304
+ function useDashboardQuery(load, requestOptions, enabled = true, refreshGeneration = 0) {
1305
+ const client = useOvClient();
1306
+ const inheritedSignal = useOvSignal();
1307
+ const { baseURL, apiKey, accountId, userId, resourceId, signal } = requestOptions ?? {};
1308
+ const [generation, setGeneration] = useState(0);
1309
+ const [state, setState] = useState({
1310
+ owner: [],
1311
+ error: null
1312
+ });
1313
+ const owner = useMemo(()=>[
1314
+ client,
1315
+ load,
1316
+ baseURL,
1317
+ apiKey,
1318
+ accountId,
1319
+ userId,
1320
+ resourceId,
1321
+ signal,
1322
+ inheritedSignal,
1323
+ generation,
1324
+ enabled,
1325
+ refreshGeneration
1326
+ ], [
1327
+ client,
1328
+ load,
1329
+ baseURL,
1330
+ apiKey,
1331
+ accountId,
1332
+ userId,
1333
+ resourceId,
1334
+ signal,
1335
+ inheritedSignal,
1336
+ generation,
1337
+ enabled,
1338
+ refreshGeneration
1339
+ ]);
1340
+ const current = owner.every((value, i)=>value === state.owner[i]);
1341
+ useEffect(()=>{
1342
+ if (!enabled) return;
1343
+ if (!client) return void setState({
1344
+ owner,
1345
+ error: new Error('OpenVikingProvider is required')
1346
+ });
1347
+ const controller = new AbortController();
1348
+ const signals = [
1349
+ controller.signal,
1350
+ signal,
1351
+ inheritedSignal
1352
+ ].filter((value)=>Boolean(value));
1353
+ const options = {
1354
+ baseURL,
1355
+ apiKey,
1356
+ accountId,
1357
+ userId,
1358
+ resourceId,
1359
+ signal: AbortSignal.any(signals)
1360
+ };
1361
+ load(client, options).then((data)=>{
1362
+ if (!options.signal.aborted) setState({
1363
+ owner,
1364
+ data,
1365
+ error: null
1366
+ });
1367
+ }, (cause)=>{
1368
+ if (!options.signal.aborted) setState({
1369
+ owner,
1370
+ error: cause instanceof Error ? cause : new Error(String(cause))
1371
+ });
1372
+ });
1373
+ return ()=>controller.abort();
1374
+ }, [
1375
+ client,
1376
+ load,
1377
+ baseURL,
1378
+ apiKey,
1379
+ accountId,
1380
+ userId,
1381
+ resourceId,
1382
+ signal,
1383
+ inheritedSignal,
1384
+ generation,
1385
+ enabled,
1386
+ owner
1387
+ ]);
1388
+ return {
1389
+ data: current ? state.data : void 0,
1390
+ loading: enabled && !current,
1391
+ error: current ? state.error : null,
1392
+ retry: ()=>setGeneration((value)=>value + 1)
1393
+ };
1394
+ }
1395
+ function useExperienceDashboard({ uri, requestOptions, enabled = true, refreshGeneration = 0 }) {
1396
+ const client = useOvClient();
1397
+ const signal = useOvSignal();
1398
+ const owner = useMemo(()=>[
1399
+ uri,
1400
+ client,
1401
+ signal,
1402
+ requestOptions?.baseURL,
1403
+ requestOptions?.apiKey,
1404
+ requestOptions?.accountId,
1405
+ requestOptions?.userId,
1406
+ requestOptions?.resourceId
1407
+ ], [
1408
+ uri,
1409
+ client,
1410
+ signal,
1411
+ requestOptions?.baseURL,
1412
+ requestOptions?.apiKey,
1413
+ requestOptions?.accountId,
1414
+ requestOptions?.userId,
1415
+ requestOptions?.resourceId
1416
+ ]);
1417
+ const [state, setState] = useState({
1418
+ owner,
1419
+ page: 0,
1420
+ days: 7,
1421
+ tab: 'impact'
1422
+ });
1423
+ const current = state.owner === owner ? state : {
1424
+ owner,
1425
+ page: 0,
1426
+ days: 7,
1427
+ tab: 'impact'
1428
+ };
1429
+ const { page, days, tab } = current;
1430
+ const setPage = (value)=>setState({
1431
+ ...current,
1432
+ page: Math.max(0, value)
1433
+ });
1434
+ const setDays = (value)=>setState({
1435
+ ...current,
1436
+ days: value,
1437
+ page: 0
1438
+ });
1439
+ const setTab = (value)=>setState({
1440
+ ...current,
1441
+ tab: value,
1442
+ page: 0
1443
+ });
1444
+ const directory = isExperienceDirectory(uri);
1445
+ const loadList = useCallback((client, options)=>readExperiences(client, uri, page, options), [
1446
+ uri,
1447
+ page
1448
+ ]);
1449
+ const loadSources = useCallback((client, options)=>readExperienceSources(client, uri, options), [
1450
+ uri
1451
+ ]);
1452
+ const loadTrajectories = useCallback(async (client, options)=>{
1453
+ const result = await client.request({
1454
+ method: 'GET',
1455
+ path: OV_PATHS.experienceTrajectories,
1456
+ query: {
1457
+ experience_uri: uri,
1458
+ offset: 20 * page,
1459
+ limit: 20,
1460
+ ...experienceDateRange(days)
1461
+ },
1462
+ options
1463
+ });
1464
+ if (!Array.isArray(result?.items) || !Number.isSafeInteger(result.total) || result.total < 0 || result.items.some((item)=>'string' != typeof item?.uri || 'string' != typeof item.name)) throw new TypeError('Invalid Experience trajectories');
1465
+ return result;
1466
+ }, [
1467
+ uri,
1468
+ page,
1469
+ days
1470
+ ]);
1471
+ const loadOutcomes = useCallback(async (client, options)=>{
1472
+ const result = await client.request({
1473
+ method: 'GET',
1474
+ path: OV_PATHS.experienceOutcomes,
1475
+ query: {
1476
+ experience_uri: uri,
1477
+ ...experienceDateRange(days)
1478
+ },
1479
+ options
1480
+ });
1481
+ if (!Array.isArray(result?.outcome_distribution) || result.outcome_distribution.some((entry)=>![
1482
+ 'success',
1483
+ 'partial',
1484
+ 'unfinished',
1485
+ 'failure',
1486
+ 'unknown'
1487
+ ].includes(entry?.outcome) || !Number.isSafeInteger(entry.count) || entry.count < 0)) throw new TypeError('Invalid Experience outcomes');
1488
+ return result.outcome_distribution;
1489
+ }, [
1490
+ uri,
1491
+ days
1492
+ ]);
1493
+ const list = useDashboardQuery(loadList, requestOptions, enabled && directory, refreshGeneration);
1494
+ const sources = useDashboardQuery(loadSources, requestOptions, enabled && !directory && 'source' === tab, refreshGeneration);
1495
+ const trajectories = useDashboardQuery(loadTrajectories, requestOptions, enabled && !directory && 'impact' === tab, refreshGeneration);
1496
+ const outcomes = useDashboardQuery(loadOutcomes, requestOptions, enabled && !directory && 'impact' === tab, refreshGeneration);
1497
+ return {
1498
+ list,
1499
+ sources,
1500
+ trajectories,
1501
+ outcomes,
1502
+ page,
1503
+ setPage,
1504
+ days,
1505
+ setDays,
1506
+ tab,
1507
+ setTab
1508
+ };
1509
+ }
1510
+ function ExperienceDashboardPane({ uri, locale, requestOptions, refreshGeneration, onOpenUri }) {
1511
+ const browser = useExperienceDashboard({
1512
+ uri,
1513
+ requestOptions,
1514
+ refreshGeneration
1515
+ });
1516
+ const directory = isExperienceDirectory(uri);
1517
+ const status = (query)=>({
1518
+ loading: query.loading,
1519
+ error: query.error,
1520
+ onRetry: query.retry
1521
+ });
1522
+ return /*#__PURE__*/ jsx(ExperienceDashboard, {
1523
+ uri: uri,
1524
+ kind: directory ? 'directory' : 'file',
1525
+ locale: locale,
1526
+ onOpenUri: onOpenUri,
1527
+ data: {
1528
+ experiences: browser.list.data?.items,
1529
+ outcomes: browser.outcomes.data,
1530
+ trajectories: browser.trajectories.data?.items,
1531
+ trajectoryTotal: browser.trajectories.data?.total,
1532
+ sources: browser.sources.data
1533
+ },
1534
+ tab: browser.tab,
1535
+ days: browser.days,
1536
+ page: browser.page,
1537
+ startIndex: 20 * browser.page,
1538
+ hasMore: Boolean(directory ? browser.list.data?.hasMore : 'impact' === browser.tab && browser.trajectories.data?.has_more),
1539
+ states: {
1540
+ list: status(browser.list),
1541
+ sources: status(browser.sources),
1542
+ outcomes: status(browser.outcomes),
1543
+ trajectories: status(browser.trajectories)
1544
+ },
1545
+ onTabChange: browser.setTab,
1546
+ onDaysChange: browser.setDays,
1547
+ onPageChange: browser.setPage
1548
+ });
1549
+ }
1550
+ const wiki_conflicts_prefix = '<!-- CONFLICTS_CONTENT';
1551
+ const manualResolutionBasis = '\u4eba\u5de5\u4fee\u590d';
1552
+ const timestamp = (value)=>'number' == typeof value && Number.isSafeInteger(value) && value >= 0 && Number.isFinite(new Date(1000 * value).getTime());
1553
+ const locateWikiConflict = (document, matchText)=>{
1554
+ const mask = (value)=>value.replace(/[^\r\n]/g, ' ');
1555
+ const body = document.replace(/^(?:\uFEFF)?---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/, mask).replace(/<!--[\s\S]*?(?:-->|$)/g, mask);
1556
+ const start = matchText ? body.indexOf(matchText) : -1;
1557
+ if (start < 0) return {
1558
+ status: 'missing'
1559
+ };
1560
+ if (body.indexOf(matchText, start + 1) >= 0) return {
1561
+ status: 'ambiguous'
1562
+ };
1563
+ return {
1564
+ status: 'unique',
1565
+ start,
1566
+ end: start + matchText.length,
1567
+ line: document.slice(0, start).split('\n').length
1568
+ };
1569
+ };
1570
+ const parseWikiFileConflicts = (content)=>{
1571
+ const start = content.lastIndexOf(wiki_conflicts_prefix);
1572
+ if (start < 0) return {
1573
+ status: 'missing'
1574
+ };
1575
+ const footer = content.slice(start).trimEnd();
1576
+ if (!footer.endsWith('-->')) return {
1577
+ status: 'invalid'
1578
+ };
1579
+ const document = content.slice(0, start);
1580
+ const source = footer.slice(wiki_conflicts_prefix.length, -3).trim();
1581
+ let parsed;
1582
+ try {
1583
+ parsed = source ? JSON.parse(`[${source}]`) : [];
1584
+ } catch {
1585
+ return {
1586
+ status: 'invalid'
1587
+ };
1588
+ }
1589
+ if (!Array.isArray(parsed)) return {
1590
+ status: 'invalid'
1591
+ };
1592
+ const records = [];
1593
+ for (const [index, raw] of parsed.entries()){
1594
+ if (!raw || 'object' != typeof raw || Array.isArray(raw)) return {
1595
+ status: 'invalid'
1596
+ };
1597
+ const value = raw;
1598
+ if ('string' != typeof value.match_text || !value.match_text || 'string' != typeof value.problem_description || 'string' != typeof value.reason || ![
1599
+ 'high',
1600
+ 'medium',
1601
+ 'low'
1602
+ ].includes(value.severity) || ![
1603
+ 'unfixed',
1604
+ 'fixed'
1605
+ ].includes(value.state) || !timestamp(value.begin_time) || 'string' != typeof value.fix_plan || 'string' != typeof value.resolution_basis) return {
1606
+ status: 'invalid'
1607
+ };
1608
+ if ('fixed' === value.state ? !timestamp(value.fix_time) || value.fix_time < value.begin_time : null !== value.fix_time) return {
1609
+ status: 'invalid'
1610
+ };
1611
+ const matchText = value.match_text;
1612
+ const location = locateWikiConflict(document, matchText);
1613
+ records.push({
1614
+ id: JSON.stringify([
1615
+ index,
1616
+ value.begin_time,
1617
+ matchText
1618
+ ]),
1619
+ matchText,
1620
+ description: value.problem_description,
1621
+ reason: value.reason,
1622
+ severity: value.severity,
1623
+ state: value.state,
1624
+ beginTime: value.begin_time,
1625
+ fixTime: value.fix_time,
1626
+ fixPlan: value.fix_plan,
1627
+ resolutionBasis: value.resolution_basis,
1628
+ resolutionSource: 'fixed' === value.state && value.resolution_basis.trim() ? value.resolution_basis.trim() === manualResolutionBasis ? 'manual' : 'automatic' : void 0,
1629
+ location
1630
+ });
1631
+ }
1632
+ return {
1633
+ status: 'available',
1634
+ document,
1635
+ records
1636
+ };
1637
+ };
1638
+ async function readWikiDashboard(client, uri, options) {
1639
+ const stat = await readDashboardStat(client, uri, options);
1640
+ uri = stat.uri;
1641
+ if (!stat.isDir) {
1642
+ const result = await client.request({
1643
+ method: 'GET',
1644
+ path: OV_PATHS.contentRead,
1645
+ query: {
1646
+ uri,
1647
+ limit: -1
1648
+ },
1649
+ options
1650
+ });
1651
+ if ('string' != typeof result) throw new TypeError('Invalid Wiki content response');
1652
+ const parsed = parseWikiFileConflicts(result);
1653
+ if ('invalid' === parsed.status) throw new TypeError('Invalid CONFLICTS_CONTENT metadata');
1654
+ return {
1655
+ kind: 'file',
1656
+ data: {
1657
+ path: uri,
1658
+ conflicts: 'available' === parsed.status ? parsed.records : []
1659
+ }
1660
+ };
1661
+ }
1662
+ const page = await client.requestEnvelope({
1663
+ method: 'GET',
1664
+ path: OV_PATHS.fsLs,
1665
+ query: {
1666
+ uri,
1667
+ recursive: true,
1668
+ output: 'agent',
1669
+ tags: [
1670
+ 'has_conflict=true'
1671
+ ],
1672
+ limit: 100,
1673
+ sort_by: 'mtime',
1674
+ sort_order: 'desc'
1675
+ },
1676
+ options
1677
+ });
1678
+ if (!Array.isArray(page.result)) throw new TypeError('Invalid Wiki conflict listing');
1679
+ const prefix = `${uri.replace(/\/+$/, '')}/`;
1680
+ const files = page.result.filter((entry)=>!entry.isDir && entry.uri.startsWith(prefix)).map((entry)=>entry.uri);
1681
+ return {
1682
+ kind: 'directory',
1683
+ data: {
1684
+ files,
1685
+ conflictFileCount: files.length,
1686
+ limited: page.hasMore || files.length >= 100
1687
+ }
1688
+ };
1689
+ }
1690
+ function useWikiDashboard({ uri, requestOptions, enabled = true, refreshGeneration = 0 }) {
1691
+ const load = useCallback((client, options)=>readWikiDashboard(client, uri, options), [
1692
+ uri
1693
+ ]);
1694
+ return useDashboardQuery(load, requestOptions, enabled, refreshGeneration);
1695
+ }
1696
+ function WikiDashboardPane({ uri, kind, requestOptions, refreshGeneration, onOpenUri, onOpenDashboard, onEdit, onDiscuss, ...props }) {
1697
+ const query = useWikiDashboard({
1698
+ uri,
1699
+ requestOptions,
1700
+ refreshGeneration
1701
+ });
1702
+ const common = {
1703
+ ...props,
1704
+ loading: query.loading,
1705
+ error: query.error,
1706
+ onRetry: query.retry
1707
+ };
1708
+ if ((query.data?.kind ?? kind) === 'file') return /*#__PURE__*/ createElement(WikiDashboard, {
1709
+ ...common,
1710
+ key: uri,
1711
+ kind: "file",
1712
+ data: query.data?.kind === 'file' ? query.data.data : void 0,
1713
+ onOpenUri: onOpenUri && (()=>onOpenUri(uri)),
1714
+ onEdit: onEdit && ((conflict)=>onEdit(uri, conflict)),
1715
+ onDiscuss: onDiscuss && ((conflict)=>onDiscuss(uri, conflict))
1716
+ });
1717
+ return /*#__PURE__*/ createElement(WikiDashboard, {
1718
+ ...common,
1719
+ key: uri,
1720
+ kind: "directory",
1721
+ data: query.data?.kind === 'directory' ? query.data.data : void 0,
1722
+ onOpenUri: onOpenUri,
1723
+ onOpenDashboard: onOpenDashboard,
1724
+ onEdit: onEdit
1725
+ });
1726
+ }
1727
+ function useBrowserDashboards({ rows, metadataRows, uri, rootUri, generation, locale, requestOptions, onOpenUri, dashboardKinds, renderDashboard, dashboardLabels }) {
1728
+ const [opened, setOpened] = useState(null);
1729
+ const load = useCallback((client, options)=>readCompileTargets(client, rootUri, options), [
1730
+ rootUri
1731
+ ]);
1732
+ const wikiEnabled = void 0 === dashboardKinds || dashboardKinds.includes('wiki');
1733
+ const catalog = useDashboardQuery(load, requestOptions, wikiEnabled, generation);
1734
+ const loadHint = useCallback(async (client, options)=>uri ? readDashboardHint(client, uri, options) : void 0, [
1735
+ uri
1736
+ ]);
1737
+ const hint = useDashboardQuery(loadHint, requestOptions, wikiEnabled && Boolean(uri), generation);
1738
+ const canonicalUri = hint.data?.uri ?? uri;
1739
+ const knownRows = canonicalUri && hint.data?.tags.includes('compile_target=true') ? [
1740
+ ...metadataRows,
1741
+ {
1742
+ kind: 'entry',
1743
+ uri: canonicalUri,
1744
+ id: canonicalUri,
1745
+ name: canonicalUri,
1746
+ depth: 0,
1747
+ type: 'directory',
1748
+ tags: hint.data.tags
1749
+ }
1750
+ ] : metadataRows;
1751
+ const unsupported = isOvClientError(catalog.error) && 404 === catalog.error.statusCode;
1752
+ const targets = catalog.data ?? [];
1753
+ const resolve = (target)=>{
1754
+ const match = resolveDashboard(target === uri ? canonicalUri ?? target : target, knownRows, targets);
1755
+ return match ? {
1756
+ ...match,
1757
+ uri: target
1758
+ } : void 0;
1759
+ };
1760
+ const dashboards = new Map(rows.filter((row)=>'entry' === row.kind).flatMap((row)=>{
1761
+ const dashboard = resolve(row.uri);
1762
+ return dashboard && (void 0 === dashboardKinds || dashboardKinds.includes(dashboard.kind)) ? [
1763
+ [
1764
+ row.uri,
1765
+ dashboard
1766
+ ]
1767
+ ] : [];
1768
+ }));
1769
+ const resolved = uri ? resolve(uri) : void 0;
1770
+ const current = resolved && (void 0 === dashboardKinds || dashboardKinds.includes(resolved.kind)) ? resolved : void 0;
1771
+ const open = useCallback((target)=>{
1772
+ setOpened(target);
1773
+ onOpenUri(target);
1774
+ }, [
1775
+ onOpenUri
1776
+ ]);
1777
+ const close = useCallback(()=>setOpened(null), []);
1778
+ const text = dashboard_labels_dashboardLabels(locale, dashboardLabels);
1779
+ const title = current?.kind === 'experience' ? text.experienceTitle : text.wikiTitle;
1780
+ const tab = current && uri ? {
1781
+ key: dashboardKey(uri),
1782
+ title,
1783
+ icon: createElement('experience' === current.kind ? ExperienceBoardIcon : WikiBoardIcon, {
1784
+ width: 14,
1785
+ height: 14
1786
+ }),
1787
+ kind: 'content',
1788
+ content: renderDashboard?.(current) ?? ('experience' === current.kind ? createElement(ExperienceDashboardPane, {
1789
+ uri,
1790
+ locale,
1791
+ requestOptions,
1792
+ refreshGeneration: generation,
1793
+ onOpenUri: (target)=>{
1794
+ setOpened(null);
1795
+ onOpenUri(target);
1796
+ }
1797
+ }) : createElement(WikiDashboardPane, {
1798
+ uri,
1799
+ kind: knownRows.some((row)=>'entry' === row.kind && row.uri === canonicalUri && 'file' === row.type) ? 'file' : 'directory',
1800
+ locale,
1801
+ requestOptions,
1802
+ refreshGeneration: generation,
1803
+ onOpenUri: (target)=>{
1804
+ setOpened(null);
1805
+ onOpenUri(target);
1806
+ },
1807
+ onOpenDashboard: open
1808
+ }))
1809
+ } : void 0;
1810
+ return {
1811
+ dashboards,
1812
+ tab,
1813
+ isOpen: null !== uri && opened === uri && Boolean(current),
1814
+ open,
1815
+ close,
1816
+ error: unsupported ? null : catalog.error,
1817
+ retry: catalog.retry
1818
+ };
1819
+ }
974
1820
  function knownDirectories(rootUri, dirs) {
975
1821
  const known = new Set([
976
1822
  rootUri
@@ -994,6 +1840,21 @@ function useKnownFile(dirs) {
994
1840
  files
995
1841
  ]);
996
1842
  }
1843
+ function useTreeMetadata(rootUri, dirs) {
1844
+ const directoryUris = useMemo(()=>knownDirectories(rootUri, dirs), [
1845
+ rootUri,
1846
+ dirs
1847
+ ]);
1848
+ const isKnownFile = useKnownFile(dirs);
1849
+ const metadataRows = useMemo(()=>Object.values(dirs).flatMap((dir)=>dir.entries.map((entry)=>toEntryRow(entry, 0, false))), [
1850
+ dirs
1851
+ ]);
1852
+ return {
1853
+ directoryUris,
1854
+ isKnownFile,
1855
+ metadataRows
1856
+ };
1857
+ }
997
1858
  const ids = new WeakMap();
998
1859
  let nextId = 1;
999
1860
  function idOf(value) {
@@ -1064,6 +1925,7 @@ async function loadPage(client, uri, offset, signal, requestOptions) {
1064
1925
  query: {
1065
1926
  uri,
1066
1927
  output: 'agent',
1928
+ include_tags: true,
1067
1929
  node_limit: PAGE_SIZE,
1068
1930
  offset
1069
1931
  },
@@ -1189,6 +2051,14 @@ function useFsTree({ client, rootUri, defaultExpandedUri, requestOptions }) {
1189
2051
  setExpanded((prev)=>new Set(prev).add(uri));
1190
2052
  if (!dirsRef.current[uri]) await fetchPage(uri, 0);
1191
2053
  },
2054
+ openPath: async (uri)=>{
2055
+ const current = scope.current();
2056
+ for (const parent of expandChain(rootUri, uri)){
2057
+ if (current.controller.signal.aborted || scope.current().key !== current.key) return;
2058
+ setExpanded((prev)=>new Set(prev).add(parent));
2059
+ if (!dirsRef.current[parent]) await fetchPage(parent, 0);
2060
+ }
2061
+ },
1192
2062
  toggleExpand: async (row)=>{
1193
2063
  const isOpen = expandedRef.current.has(row.uri);
1194
2064
  setExpanded((prev)=>{
@@ -1226,21 +2096,16 @@ function useFsTree({ client, rootUri, defaultExpandedUri, requestOptions }) {
1226
2096
  dirs,
1227
2097
  expanded
1228
2098
  ]);
1229
- const directoryUris = useMemo(()=>knownDirectories(rootUri, dirs), [
1230
- rootUri,
1231
- dirs
1232
- ]);
1233
- const isKnownFile = useKnownFile(dirs);
2099
+ const metadata = useTreeMetadata(rootUri, dirs);
1234
2100
  return {
1235
2101
  rows,
2102
+ ...metadata,
1236
2103
  isReady,
1237
2104
  error,
1238
- directoryUris,
1239
- isKnownFile,
1240
2105
  ...state
1241
2106
  };
1242
2107
  }
1243
- function useFileBrowser({ rootUri, locale: localeProp, controlled = false, summaryLabels, formatCharacterCount, defaultExpandedUri, currentUri, requestOptions, onUriChange }) {
2108
+ function useFileBrowser({ rootUri, locale: localeProp, dashboardKinds, renderDashboard, dashboardLabels, controlled = false, summaryLabels, formatCharacterCount, defaultExpandedUri, currentUri, requestOptions, onUriChange }) {
1244
2109
  const locale = useLocale(localeProp);
1245
2110
  const client = useOvClient();
1246
2111
  const [query, setQuery] = useState('');
@@ -1289,17 +2154,36 @@ function useFileBrowser({ rootUri, locale: localeProp, controlled = false, summa
1289
2154
  search.results,
1290
2155
  tree.rows
1291
2156
  ]);
1292
- const selectedId = useMemo(()=>{
1293
- if (!activeUri || null === search.results) return activeUri;
1294
- const index = search.results.findIndex((entry)=>entry.uri === activeUri);
1295
- return index < 0 ? activeUri : `${activeUri}#${index}`;
1296
- }, [
1297
- activeUri,
1298
- search.results
2157
+ const openPath = tree.openPath;
2158
+ const openResource = useMemo(()=>async (uri)=>{
2159
+ setPendingUri(uri);
2160
+ notifyUriChange.current(uri);
2161
+ const directory = tree.directoryUris.has(uri) ? uri : uri.slice(0, uri.lastIndexOf('/'));
2162
+ await openPath(directory);
2163
+ }, [
2164
+ openPath,
2165
+ tree.directoryUris
1299
2166
  ]);
2167
+ const boards = useBrowserDashboards({
2168
+ rows,
2169
+ metadataRows: tree.metadataRows,
2170
+ uri: activeUri,
2171
+ rootUri,
2172
+ generation: refreshGeneration,
2173
+ locale,
2174
+ requestOptions,
2175
+ onOpenUri: openResource,
2176
+ dashboardKinds,
2177
+ renderDashboard,
2178
+ dashboardLabels
2179
+ });
2180
+ const selectedId = selectedRowId(activeUri, search.results);
1300
2181
  const { open, toggleExpand, loadMore, retry, refresh } = tree;
2182
+ const { open: openDashboard, close: closeDashboard } = boards;
2183
+ const setActiveKey = preview.setActiveKey;
1301
2184
  const actions = useMemo(()=>({
1302
2185
  select: async (row)=>{
2186
+ closeDashboard();
1303
2187
  setPendingUri(row.uri);
1304
2188
  notifyUriChange.current(row.uri);
1305
2189
  if ('directory' === row.type) await open(row.uri);
@@ -1322,15 +2206,25 @@ function useFileBrowser({ rootUri, locale: localeProp, controlled = false, summa
1322
2206
  setRefreshing(false);
1323
2207
  }
1324
2208
  },
1325
- setPreviewKey: preview.setActiveKey,
2209
+ openDashboard: openDashboard,
2210
+ setPreviewKey: (key)=>{
2211
+ if (activeUri && key === dashboardKey(activeUri)) openDashboard(activeUri);
2212
+ else {
2213
+ closeDashboard();
2214
+ setActiveKey(key);
2215
+ }
2216
+ },
1326
2217
  setQuery
1327
2218
  }), [
1328
- preview.setActiveKey,
2219
+ setActiveKey,
1329
2220
  open,
1330
2221
  toggleExpand,
1331
2222
  loadMore,
1332
2223
  retry,
1333
- refresh
2224
+ refresh,
2225
+ openDashboard,
2226
+ closeDashboard,
2227
+ activeUri
1334
2228
  ]);
1335
2229
  return {
1336
2230
  rows,
@@ -1341,8 +2235,13 @@ function useFileBrowser({ rootUri, locale: localeProp, controlled = false, summa
1341
2235
  treeError: tree.error,
1342
2236
  previewError: preview.error,
1343
2237
  searchError: search.error,
1344
- previewTabs: preview.tabs,
1345
- previewKey: preview.activeKey,
2238
+ dashboards: boards.dashboards,
2239
+ dashboardError: boards.error,
2240
+ previewTabs: boards.tab ? [
2241
+ ...preview.tabs ?? [],
2242
+ boards.tab
2243
+ ] : preview.tabs,
2244
+ previewKey: boards.isOpen && activeUri ? dashboardKey(activeUri) : preview.activeKey,
1346
2245
  previewFooter: preview.footer,
1347
2246
  query,
1348
2247
  searching: null !== search.results,
@@ -1351,7 +2250,7 @@ function useFileBrowser({ rootUri, locale: localeProp, controlled = false, summa
1351
2250
  actions
1352
2251
  };
1353
2252
  }
1354
- function FileBrowser({ uri, defaultUri, rootUri = 'viking://', defaultExpandedUri, locale: localeProp, agentConnect, onUriChange, onImportResources, renderImportResources, labels, requestOptions, className, style, ...rest }) {
2253
+ function FileBrowser({ uri, defaultUri, rootUri = 'viking://', defaultExpandedUri, locale: localeProp, agentConnect, dashboardKinds, renderDashboard, onUriChange, onImportResources, renderImportResources, labels, requestOptions, className, style, ...rest }) {
1355
2254
  const locale = useLocale(localeProp);
1356
2255
  const [internalUri, setInternalUri] = useState(()=>defaultUri ?? null);
1357
2256
  const [preferredTreeWidth, setPreferredTreeWidth] = useState(352);
@@ -1385,6 +2284,9 @@ function FileBrowser({ uri, defaultUri, rootUri = 'viking://', defaultExpandedUr
1385
2284
  const browser = useFileBrowser({
1386
2285
  rootUri,
1387
2286
  locale,
2287
+ dashboardKinds,
2288
+ renderDashboard,
2289
+ dashboardLabels: labels?.dashboard,
1388
2290
  controlled: isControlled,
1389
2291
  summaryLabels,
1390
2292
  formatCharacterCount: text.formatCharacterCount,
@@ -1420,6 +2322,7 @@ function FileBrowser({ uri, defaultUri, rootUri = 'viking://', defaultExpandedUr
1420
2322
  browser: browser,
1421
2323
  treeLabels: text.tree,
1422
2324
  locale: locale,
2325
+ dashboardLabels: labels?.dashboard,
1423
2326
  searchPlaceholder: text.searchPlaceholder,
1424
2327
  refreshLabel: text.refresh,
1425
2328
  searchLoading: text.searchLoading,
@@ -1469,4 +2372,4 @@ function FileBrowser({ uri, defaultUri, rootUri = 'viking://', defaultExpandedUr
1469
2372
  })
1470
2373
  });
1471
2374
  }
1472
- export { FileBrowser, useFileBrowser };
2375
+ export { FileBrowser };