@karmaniverous/jeeves-server 3.5.2 → 3.6.1

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 (82) hide show
  1. package/.tsbuildinfo +1 -1
  2. package/CHANGELOG.md +33 -1
  3. package/client/src/components/DirectoryRow.tsx +5 -1
  4. package/client/src/components/FileContentView.tsx +7 -0
  5. package/client/src/components/MarkdownView.tsx +63 -22
  6. package/client/src/components/TocSection.tsx +74 -0
  7. package/client/src/components/renderableUtils.ts +2 -2
  8. package/client/src/components/tocUtils.ts +65 -0
  9. package/client/src/index.css +36 -0
  10. package/client/src/lib/api.ts +2 -1
  11. package/client/src/lucide.d.ts +15 -0
  12. package/dist/client/assets/{CodeViewer-Cegj3cEn.js → CodeViewer-D5fJ1Z6_.js} +1 -1
  13. package/dist/client/assets/{index-DrBXupPz.js → index-BXy6kgl7.js} +2 -2
  14. package/dist/client/assets/index-Ch0vkF39.css +2 -0
  15. package/dist/client/index.html +2 -2
  16. package/dist/src/cli/index.js +2 -1
  17. package/dist/src/cli/start-server.js +12 -2
  18. package/dist/src/config/index.js +16 -2
  19. package/dist/src/config/loadConfig.test.js +66 -1
  20. package/dist/src/config/resolve.js +0 -4
  21. package/dist/src/config/resolve.test.js +0 -2
  22. package/dist/src/config/schema.js +7 -21
  23. package/dist/src/config/substituteEnvVars.js +2 -0
  24. package/dist/src/descriptor.js +9 -2
  25. package/dist/src/routes/api/auth-status.js +1 -1
  26. package/dist/src/routes/api/directory.js +46 -24
  27. package/dist/src/routes/api/directory.test.js +65 -0
  28. package/dist/src/routes/api/export.js +5 -1
  29. package/dist/src/routes/api/export.test.js +46 -0
  30. package/dist/src/routes/api/fileContent.js +26 -4
  31. package/dist/src/routes/api/runner.js +2 -3
  32. package/dist/src/routes/api/runner.test.js +29 -0
  33. package/dist/src/routes/api/search.js +4 -9
  34. package/dist/src/routes/api/search.test.js +28 -0
  35. package/dist/src/routes/config.test.js +0 -1
  36. package/dist/src/routes/event.js +1 -1
  37. package/dist/src/routes/status.js +4 -4
  38. package/dist/src/routes/status.test.js +10 -4
  39. package/dist/src/server.js +4 -2
  40. package/dist/src/services/csv.js +114 -0
  41. package/dist/src/services/csv.test.js +107 -0
  42. package/dist/src/services/markdown.js +21 -1
  43. package/dist/src/services/markdown.test.js +43 -0
  44. package/dist/src/util/packageVersion.js +3 -13
  45. package/guides/deployment.md +1 -1
  46. package/guides/setup.md +14 -10
  47. package/knip.json +2 -1
  48. package/package.json +18 -16
  49. package/src/cli/index.ts +3 -1
  50. package/src/cli/start-server.ts +17 -3
  51. package/src/config/index.ts +22 -2
  52. package/src/config/loadConfig.test.ts +77 -1
  53. package/src/config/resolve.test.ts +0 -2
  54. package/src/config/resolve.ts +0 -4
  55. package/src/config/schema.ts +8 -21
  56. package/src/config/substituteEnvVars.ts +2 -0
  57. package/src/config/types.ts +0 -4
  58. package/src/descriptor.ts +9 -1
  59. package/src/routes/api/auth-status.ts +1 -1
  60. package/src/routes/api/directory.test.ts +77 -0
  61. package/src/routes/api/directory.ts +59 -22
  62. package/src/routes/api/export.test.ts +56 -0
  63. package/src/routes/api/export.ts +5 -1
  64. package/src/routes/api/fileContent.ts +27 -3
  65. package/src/routes/api/runner.test.ts +39 -0
  66. package/src/routes/api/runner.ts +2 -5
  67. package/src/routes/api/search.test.ts +36 -0
  68. package/src/routes/api/search.ts +4 -9
  69. package/src/routes/config.test.ts +0 -1
  70. package/src/routes/event.test.ts +4 -4
  71. package/src/routes/event.ts +11 -6
  72. package/src/routes/status.test.ts +13 -4
  73. package/src/routes/status.ts +4 -4
  74. package/src/server.ts +4 -2
  75. package/src/services/csv.test.ts +127 -0
  76. package/src/services/csv.ts +115 -0
  77. package/src/services/markdown.test.ts +54 -0
  78. package/src/services/markdown.ts +21 -1
  79. package/src/types/puppeteer-core.d.ts +16 -0
  80. package/src/util/packageVersion.ts +3 -18
  81. package/dist/client/assets/index-Dk_myGs4.css +0 -2
  82. package/src/types/jsonmap.d.ts +0 -10
package/CHANGELOG.md CHANGED
@@ -2,13 +2,45 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. Dates are displayed in UTC.
4
4
 
5
- #### [3.5.2](https://github.com/karmaniverous/jeeves-server/compare/service/3.5.1...3.5.2)
5
+ #### [3.6.1](https://github.com/karmaniverous/jeeves-server/compare/service/3.6.0...3.6.1)
6
+
7
+ - chore: dependency updates — jeeves ^0.5.3, jsonmap ^2.1.1, @types/node ^25.5.2, and minor/patch bumps [`#149`](https://github.com/karmaniverous/jeeves-server/pull/149)
8
+ - chore: update @karmaniverous/jeeves to ^0.5.3 and bump minor/patch deps [`a73fd37`](https://github.com/karmaniverous/jeeves-server/commit/a73fd37cbdc235bd4d4a1ad011eac03d330755f6)
9
+ - chore: update @types/node to ^25.5.2 [`696da29`](https://github.com/karmaniverous/jeeves-server/commit/696da297e5840a1c2ef72f0e5eee5df754144925)
10
+ - chore: release @karmaniverous/jeeves-server-openclaw v0.7.0 [`3cce7a2`](https://github.com/karmaniverous/jeeves-server/commit/3cce7a2e8d97eb111ed3a5c82695cb11b7d9c54a)
11
+ - chore: update @karmaniverous/jsonmap ^0.3.1 → ^2.1.1 [`12a848d`](https://github.com/karmaniverous/jeeves-server/commit/12a848d0efd77d7ae69bb2c03f1e887300f4ba51)
12
+ - npm audit fix [`020edff`](https://github.com/karmaniverous/jeeves-server/commit/020edff96c18b52f39b67f30768b7b6821ffd227)
13
+
14
+ #### [service/3.6.0](https://github.com/karmaniverous/jeeves-server/compare/service/3.5.2...service/3.6.0)
15
+
16
+ > 3 April 2026
17
+
18
+ - feat: v3.6.0 core v0.5.1 adoption + core service alignment + UX improvements [`#146`](https://github.com/karmaniverous/jeeves-server/pull/146)
19
+ - [360] feat: Phase 3 — UX improvements (CSV tables, directory item counts, collapsible frontmatter, collapsible TOC) [`#48`](https://github.com/karmaniverous/jeeves-server/issues/48)
20
+ - [360] feat: Phase 1 — core service adoption (getServiceUrl, getBindAddress, Node 22 fast-fail) [`#135`](https://github.com/karmaniverous/jeeves-server/issues/135)
21
+ - [360] test: add missing test coverage for v3.6.0 features [`c23f7a1`](https://github.com/karmaniverous/jeeves-server/commit/c23f7a1dfa03517d2ed8a28eb0ef06da05379e38)
22
+ - [360] refactor: address code review — async directory reads, CSV row normalization, extract mapDirectoryEntry [`3a1a389`](https://github.com/karmaniverous/jeeves-server/commit/3a1a389ee5f3a4ce1d8e28db204784ea9fefd1d5)
23
+ - npm audit fix [`0be936a`](https://github.com/karmaniverous/jeeves-server/commit/0be936a6dbbfb07b1f7d62e334d307a0fb0db3f8)
24
+ - [360] refactor: split TocSection.tsx — move utilities to tocUtils.ts (fixes react-refresh lint) [`d8e8d57`](https://github.com/karmaniverous/jeeves-server/commit/d8e8d57df8eefc419bdcafed08f0a60ccd4e481d)
25
+ - [360] chore: remove TASK.md build artifact, DRY up Node version check and fix minor issues [`3e569db`](https://github.com/karmaniverous/jeeves-server/commit/3e569dbca069ea659ee5b1ca9030caa43ae4fea5)
26
+ - [360] docs: Phase 4 — documentation updates for v3.6.0 [`8e0be64`](https://github.com/karmaniverous/jeeves-server/commit/8e0be6472e300c6117dc0c917b76686015931a85)
27
+ - [360] feat: Phase 2 — core v0.5.1 wiring (cleanup escalation, getPackageVersion, substituteEnvVars TODO) [`2b97c20`](https://github.com/karmaniverous/jeeves-server/commit/2b97c209c0caaf99d27f36a6dff397ddbb4e168d)
28
+ - chore: release @karmaniverous/jeeves-server-openclaw v0.6.2 [`f120979`](https://github.com/karmaniverous/jeeves-server/commit/f1209795471958090f3fb18b4eda4bb62415da36)
29
+ - chore: release @karmaniverous/jeeves-server v3.6.0 [`40341f4`](https://github.com/karmaniverous/jeeves-server/commit/40341f4fb51177f7406c6f7071eec5131ff1ee1f)
30
+ - [360] chore: Phase 5 — quality gates clean (knip fixes, remove package-directory dep) [`a436c46`](https://github.com/karmaniverous/jeeves-server/commit/a436c462311b07bf5975e6ba8b0d75c2e216e637)
31
+ - [360] ci: drop Node 20 from Linux compatibility matrix (engine floor is now 22) [`5b690e6`](https://github.com/karmaniverous/jeeves-server/commit/5b690e6644e1e4dd2c7f66e089cf4d190daeeb32)
32
+ - [360] docs: fix Node version in deployment guide (20 → 22) [`dde5cd2`](https://github.com/karmaniverous/jeeves-server/commit/dde5cd2f3d876a8227be823de78ccbda003e9f62)
33
+
34
+ #### [service/3.5.2](https://github.com/karmaniverous/jeeves-server/compare/service/3.5.1...service/3.5.2)
35
+
36
+ > 31 March 2026
6
37
 
7
38
  - [53] Fix pre-existing lint + knip errors (Commander type resolution) [`#140`](https://github.com/karmaniverous/jeeves-server/pull/140)
8
39
  - [53] Integrate core v0.4.6: init() before descriptor.run() + knip fixes [`#139`](https://github.com/karmaniverous/jeeves-server/pull/139)
9
40
  - [53] chore: bump core to v0.4.6 (init before run) + fix knip issues [`e5f466a`](https://github.com/karmaniverous/jeeves-server/commit/e5f466a0df574e1052601fc99b0110aaf2136666)
10
41
  - [53] chore: bump core to v0.4.6 (init before run) + fix knip issues [`cc2adb6`](https://github.com/karmaniverous/jeeves-server/commit/cc2adb6c4d2a540a145bdcb04a712b2f6b77fa9e)
11
42
  - chore: release @karmaniverous/jeeves-server-openclaw v0.6.1 [`b0a4bec`](https://github.com/karmaniverous/jeeves-server/commit/b0a4bec47f5141d13e59070683f7d459e82a9faa)
43
+ - chore: release @karmaniverous/jeeves-server v3.5.2 [`08f6a37`](https://github.com/karmaniverous/jeeves-server/commit/08f6a37a4bc2802b9c1e65a2ed38482fffa2678b)
12
44
 
13
45
  #### [service/3.5.1](https://github.com/karmaniverous/jeeves-server/compare/service/3.5.0...service/3.5.1)
14
46
 
@@ -55,7 +55,11 @@ export function DirectoryRow({ entry, basePath, isInsider, shareSettings, onShar
55
55
  </div>
56
56
  </td>
57
57
  <td className="px-4 py-2.5 text-muted-foreground text-sm">{typeLabel}</td>
58
- <td className="px-4 py-2.5 text-muted-foreground text-sm">{formatSize(entry.size)}</td>
58
+ <td className="px-4 py-2.5 text-muted-foreground text-sm">
59
+ {isDir
60
+ ? (entry.itemCount != null ? `${entry.itemCount} ${entry.itemCount === 1 ? 'item' : 'items'}` : '-')
61
+ : formatSize(entry.size)}
62
+ </td>
59
63
  <td className="px-4 py-2.5 text-muted-foreground text-sm">{entry.mtime ?? '-'}</td>
60
64
  </tr>
61
65
  );
@@ -115,6 +115,13 @@ export function FileContentView({
115
115
  />
116
116
  )}
117
117
 
118
+ {/* CSV */}
119
+ {fileRendered?.type === 'csv' && fileRendered.html && activeTab === 'rendered' && (
120
+ <div className={`prose prose-sm dark:prose-invert max-w-none ${proseWidth === 'narrow' ? 'max-w-prose mx-auto' : proseWidth === 'medium' ? 'max-w-4xl mx-auto' : ''}`}>
121
+ <div className="overflow-x-auto" dangerouslySetInnerHTML={{ __html: fileRendered.html }} />
122
+ </div>
123
+ )}
124
+
118
125
  {/* SVG */}
119
126
  {fileRendered?.type === 'svg' && fileRendered.content && activeTab === 'rendered' && (
120
127
  <SvgViewer content={fileRendered.content} />
@@ -1,10 +1,14 @@
1
1
  /**
2
- * Markdown rendered view with TOC sidebar.
2
+ * Markdown rendered view with collapsible TOC sidebar.
3
3
  */
4
- import type { FileContent } from '@/lib/api';
4
+ import { useCallback, useMemo, useState } from 'react';
5
+
5
6
  import { initEmbeddedDiagramPanzoom } from '@/components/EmbeddedDiagramPanzoom';
6
7
  import { initLazyDiagrams } from '@/components/LazyDiagram';
7
8
  import { initInlineSvgPanzoom } from '@/components/InlineSvgPanzoom';
9
+ import { TocSection } from '@/components/TocSection';
10
+ import { buildTocTree, findAncestorSlugs } from '@/components/tocUtils';
11
+ import type { FileContent } from '@/lib/api';
8
12
  import { initCodeBlockCm6 } from '@/lib/codeBlockCm6';
9
13
  import { injectCopyButtons } from '@/lib/codeBlockCopy';
10
14
  import { useTheme } from '@/lib/theme';
@@ -27,6 +31,47 @@ export function MarkdownView({
27
31
  const plainCode = new URLSearchParams(window.location.search).has('plain_code');
28
32
  const hasHeadings = fileRendered.headings && fileRendered.headings.length > 2;
29
33
 
34
+ // Build TOC tree and collapse state
35
+ const tocTree = useMemo(
36
+ () => (hasHeadings ? buildTocTree(fileRendered.headings!) : []),
37
+ [fileRendered.headings, hasHeadings],
38
+ );
39
+
40
+ const [collapsed, setCollapsed] = useState<Set<string>>(() => new Set());
41
+
42
+ const toggleCollapse = useCallback((slug: string) => {
43
+ setCollapsed((prev) => {
44
+ const next = new Set(prev);
45
+ if (next.has(slug)) next.delete(slug);
46
+ else next.add(slug);
47
+ return next;
48
+ });
49
+ }, []);
50
+
51
+ const scrollToHeading = useCallback(
52
+ (slug: string) => {
53
+ // Auto-expand ancestors if collapsed
54
+ const ancestors = findAncestorSlugs(tocTree, slug);
55
+ if (ancestors.some((a) => collapsed.has(a))) {
56
+ setCollapsed((prev) => {
57
+ const next = new Set(prev);
58
+ for (const a of ancestors) next.delete(a);
59
+ return next;
60
+ });
61
+ }
62
+ scrollToIdInContainer(mainRef.current, slug);
63
+ },
64
+ [tocTree, collapsed, mainRef],
65
+ );
66
+
67
+ const mobileScrollTo = useCallback(
68
+ (slug: string) => {
69
+ scrollToHeading(slug);
70
+ setMobileTocOpen(false);
71
+ },
72
+ [scrollToHeading, setMobileTocOpen],
73
+ );
74
+
30
75
  return (
31
76
  <>
32
77
  {/* Mobile TOC overlay */}
@@ -38,16 +83,14 @@ export function MarkdownView({
38
83
  style={{ top: `${topBarHeight + 4}px` }}
39
84
  >
40
85
  <nav>
41
- {fileRendered.headings!.map((h) => (
42
- <button
43
- key={h.slug}
44
- type="button"
45
- onClick={() => { scrollToIdInContainer(mainRef.current, h.slug); setMobileTocOpen(false); }}
46
- className="block text-left text-sm text-muted-foreground hover:text-foreground cursor-pointer py-1 transition-colors w-full"
47
- style={{ paddingLeft: `${(h.level - 1) * 0.75}rem` }}
48
- >
49
- {h.text}
50
- </button>
86
+ {tocTree.map((node) => (
87
+ <TocSection
88
+ key={node.heading.slug}
89
+ node={node}
90
+ collapsed={collapsed}
91
+ toggleCollapse={toggleCollapse}
92
+ scrollTo={mobileScrollTo}
93
+ />
51
94
  ))}
52
95
  </nav>
53
96
  </div>
@@ -63,16 +106,14 @@ export function MarkdownView({
63
106
  >
64
107
  <div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2">Contents</div>
65
108
  <nav className="border-l border-border pl-3">
66
- {fileRendered.headings!.map((h) => (
67
- <button
68
- key={h.slug}
69
- type="button"
70
- onClick={() => scrollToIdInContainer(mainRef.current, h.slug)}
71
- className="block text-left text-sm text-muted-foreground hover:text-foreground cursor-pointer py-0.5 transition-colors"
72
- style={{ paddingLeft: `${(h.level - 1) * 0.75}rem` }}
73
- >
74
- {h.text}
75
- </button>
109
+ {tocTree.map((node) => (
110
+ <TocSection
111
+ key={node.heading.slug}
112
+ node={node}
113
+ collapsed={collapsed}
114
+ toggleCollapse={toggleCollapse}
115
+ scrollTo={scrollToHeading}
116
+ />
76
117
  ))}
77
118
  </nav>
78
119
  </aside>
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Recursive collapsible TOC section component.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ import { ChevronRight } from 'lucide-react';
7
+
8
+ import type { TocNode } from './tocUtils.js';
9
+
10
+ interface TocSectionProps {
11
+ node: TocNode;
12
+ collapsed: Set<string>;
13
+ toggleCollapse: (slug: string) => void;
14
+ activeSlug?: string;
15
+ scrollTo: (slug: string) => void;
16
+ }
17
+
18
+ export function TocSection({
19
+ node,
20
+ collapsed,
21
+ toggleCollapse,
22
+ activeSlug,
23
+ scrollTo,
24
+ }: TocSectionProps) {
25
+ const hasChildren = node.children.length > 0;
26
+ const isCollapsed = collapsed.has(node.heading.slug);
27
+ const isActive = activeSlug === node.heading.slug;
28
+
29
+ return (
30
+ <div>
31
+ <div className="flex items-center">
32
+ {hasChildren ? (
33
+ <button
34
+ type="button"
35
+ onClick={() => toggleCollapse(node.heading.slug)}
36
+ className="w-4 h-4 flex items-center justify-center text-muted-foreground shrink-0"
37
+ >
38
+ <ChevronRight
39
+ className={`h-3 w-3 transition-transform ${isCollapsed ? '' : 'rotate-90'}`}
40
+ />
41
+ </button>
42
+ ) : (
43
+ <span className="w-4 shrink-0" />
44
+ )}
45
+ <button
46
+ type="button"
47
+ onClick={() => scrollTo(node.heading.slug)}
48
+ className={`text-left text-sm py-0.5 transition-colors truncate ${
49
+ isActive
50
+ ? 'text-foreground font-medium'
51
+ : 'text-muted-foreground hover:text-foreground'
52
+ }`}
53
+ style={{ paddingLeft: `${(node.heading.level - 1) * 0.75}rem` }}
54
+ >
55
+ {node.heading.text}
56
+ </button>
57
+ </div>
58
+ {hasChildren && !isCollapsed && (
59
+ <div>
60
+ {node.children.map((child) => (
61
+ <TocSection
62
+ key={child.heading.slug}
63
+ node={child}
64
+ collapsed={collapsed}
65
+ toggleCollapse={toggleCollapse}
66
+ activeSlug={activeSlug}
67
+ scrollTo={scrollTo}
68
+ />
69
+ ))}
70
+ </div>
71
+ )}
72
+ </div>
73
+ );
74
+ }
@@ -3,10 +3,10 @@
3
3
  */
4
4
  import type { FileContent } from '@/lib/api';
5
5
 
6
- const RENDERABLE_EXTENSIONS = new Set(['.md', '.svg', '.mmd', '.puml', '.plantuml', '.pu']);
6
+ const RENDERABLE_EXTENSIONS = new Set(['.md', '.csv', '.svg', '.mmd', '.puml', '.plantuml', '.pu']);
7
7
 
8
8
  export function isRenderable(file: FileContent): boolean {
9
- return file.type === 'markdown' || file.type === 'svg' || file.type === 'mermaid' || file.type === 'plantuml' || !!file.html;
9
+ return file.type === 'markdown' || file.type === 'csv' || file.type === 'svg' || file.type === 'mermaid' || file.type === 'plantuml' || !!file.html;
10
10
  }
11
11
 
12
12
  export function isRenderableExt(reqPath: string): boolean {
@@ -0,0 +1,65 @@
1
+ /**
2
+ * TOC tree utilities for building and navigating heading hierarchies.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+
7
+ export interface TocNode {
8
+ heading: { level: number; text: string; slug: string };
9
+ children: TocNode[];
10
+ }
11
+
12
+ /**
13
+ * Build a tree of TocNodes from a flat heading list.
14
+ */
15
+ export function buildTocTree(
16
+ headings: { level: number; text: string; slug: string }[],
17
+ ): TocNode[] {
18
+ const root: TocNode[] = [];
19
+ const stack: TocNode[] = [];
20
+
21
+ for (const heading of headings) {
22
+ const node: TocNode = { heading, children: [] };
23
+
24
+ // Pop stack until we find a parent with a lower level
25
+ while (
26
+ stack.length > 0 &&
27
+ stack[stack.length - 1]!.heading.level >= heading.level
28
+ ) {
29
+ stack.pop();
30
+ }
31
+
32
+ if (stack.length === 0) {
33
+ root.push(node);
34
+ } else {
35
+ stack[stack.length - 1]!.children.push(node);
36
+ }
37
+
38
+ stack.push(node);
39
+ }
40
+
41
+ return root;
42
+ }
43
+
44
+ /**
45
+ * Find all ancestor slugs for a given slug in the tree.
46
+ */
47
+ export function findAncestorSlugs(
48
+ nodes: TocNode[],
49
+ targetSlug: string,
50
+ ): string[] {
51
+ const path: string[] = [];
52
+
53
+ function walk(list: TocNode[]): boolean {
54
+ for (const node of list) {
55
+ if (node.heading.slug === targetSlug) return true;
56
+ path.push(node.heading.slug);
57
+ if (walk(node.children)) return true;
58
+ path.pop();
59
+ }
60
+ return false;
61
+ }
62
+
63
+ walk(nodes);
64
+ return path;
65
+ }
@@ -279,3 +279,39 @@ pre:hover .code-copy-btn {
279
279
  .diagram-retry-btn:hover {
280
280
  background: var(--color-accent);
281
281
  }
282
+
283
+ /* CSV table styles */
284
+ .csv-table {
285
+ width: 100%;
286
+ border-collapse: collapse;
287
+ font-size: 0.875rem;
288
+ line-height: 1.5;
289
+ }
290
+ .csv-table th,
291
+ .csv-table td {
292
+ border: 1px solid var(--color-border);
293
+ padding: 0.5rem 0.75rem;
294
+ text-align: left;
295
+ }
296
+ .csv-table th {
297
+ font-weight: 600;
298
+ background: var(--color-muted);
299
+ }
300
+ .csv-table tbody tr:nth-child(even) {
301
+ background: color-mix(in srgb, var(--color-muted) 40%, transparent);
302
+ }
303
+
304
+ /* Collapsible frontmatter */
305
+ .frontmatter-collapsible .frontmatter-full { display: none; }
306
+ .frontmatter-collapsible .frontmatter-preview { display: block; }
307
+ .frontmatter-collapsible.frontmatter-expanded .frontmatter-full { display: block; }
308
+ .frontmatter-collapsible.frontmatter-expanded .frontmatter-preview { display: none; }
309
+ .frontmatter-toggle {
310
+ font-size: 0.75rem;
311
+ color: var(--color-muted-foreground);
312
+ cursor: pointer;
313
+ padding: 0.25rem 0;
314
+ border: none;
315
+ background: none;
316
+ }
317
+ .frontmatter-toggle:hover { text-decoration: underline; }
@@ -37,6 +37,7 @@ export interface DirectoryEntry {
37
37
  ext: string;
38
38
  size: number | null;
39
39
  mtime: string | null;
40
+ itemCount?: number | null;
40
41
  }
41
42
 
42
43
  export interface DirectoryListing {
@@ -59,7 +60,7 @@ export interface DriveEntry {
59
60
  }
60
61
 
61
62
  export interface FileContent {
62
- type: 'markdown' | 'text' | 'svg' | 'mermaid' | 'plantuml' | 'image' | 'binary';
63
+ type: 'markdown' | 'text' | 'csv' | 'svg' | 'mermaid' | 'plantuml' | 'image' | 'binary';
63
64
  content?: string;
64
65
  html?: string;
65
66
  headings?: { level: number; text: string; slug: string }[];
@@ -0,0 +1,15 @@
1
+ // lucide 1.x ships without declaration files. Minimal shim for the
2
+ // icons and helpers used by this project.
3
+ declare module 'lucide' {
4
+ type IconNode = [string, Record<string, string>, IconNode[]?][];
5
+
6
+ export function createElement(
7
+ iconNode: IconNode,
8
+ attrs?: Record<string, string | number>,
9
+ ): SVGSVGElement;
10
+
11
+ export const Copy: IconNode;
12
+ export const Check: IconNode;
13
+ export const Maximize: IconNode;
14
+ export const Minimize: IconNode;
15
+ }
@@ -1 +1 @@
1
- import{i as e,l as t,n,o as r,r as i,t as a}from"./theme-CPpIxvB0.js";import{n as o,t as s}from"./index-DrBXupPz.js";var c=o(`copy`,[[`rect`,{width:`14`,height:`14`,x:`8`,y:`8`,rx:`2`,ry:`2`,key:`17jyea`}],[`path`,{d:`M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2`,key:`zix9uf`}]]),l=t(r(),1),u=e();function d({content:e,fileName:t}){let r=(0,l.useRef)(null),o=(0,l.useRef)(null),[d,f]=(0,l.useState)(!0),[p,m]=(0,l.useState)(!1),[h]=a(),g=async()=>{await navigator.clipboard.writeText(e),m(!0),setTimeout(()=>m(!1),1500)};return(0,l.useEffect)(()=>{if(!r.current)return;let a=!1;return(async()=>{let{EditorView:s,EditorState:c,basicSetup:l,oneDark:u}=await i();if(a)return;let d=await n(t.split(`.`).pop()??``);if(a)return;let p=[l,c.readOnly.of(!0),s.editable.of(!1),s.theme({"&":{fontSize:`14px`},".cm-scroller":{overflow:`auto`},".cm-content":{fontFamily:`'JetBrains Mono', 'Fira Code', 'Consolas', monospace`},".cm-gutters":{fontFamily:`'JetBrains Mono', 'Fira Code', 'Consolas', monospace`},".cm-cursor":{display:`none`}})];h===`dark`&&p.push(u),d&&p.push(d),o.current=new s({state:c.create({doc:e,extensions:p}),parent:r.current}),f(!1)})(),()=>{a=!0,o.current&&=(o.current.destroy(),null)}},[e,t,h]),(0,u.jsxs)(`div`,{className:`relative group rounded-lg border border-border overflow-hidden`,children:[(0,u.jsx)(`div`,{className:`absolute top-2 right-2 z-10`,children:(0,u.jsx)(`button`,{onClick:()=>void g(),className:`p-1.5 rounded bg-accent hover:bg-accent/80 text-muted-foreground hover:text-foreground opacity-0 group-hover:opacity-100 transition-all`,title:`Copy to clipboard`,children:p?(0,u.jsx)(s,{className:`h-3.5 w-3.5 text-green-400`}):(0,u.jsx)(c,{className:`h-3.5 w-3.5`})})}),(0,u.jsx)(`div`,{ref:r,children:d&&(0,u.jsx)(`pre`,{className:`p-4 text-sm text-muted-foreground bg-muted`,children:(0,u.jsxs)(`code`,{children:[e.slice(0,200),`…`]})})})]})}export{d as CodeViewer};
1
+ import{i as e,l as t,n,o as r,r as i,t as a}from"./theme-CPpIxvB0.js";import{n as o,t as s}from"./index-BXy6kgl7.js";var c=o(`copy`,[[`rect`,{width:`14`,height:`14`,x:`8`,y:`8`,rx:`2`,ry:`2`,key:`17jyea`}],[`path`,{d:`M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2`,key:`zix9uf`}]]),l=t(r(),1),u=e();function d({content:e,fileName:t}){let r=(0,l.useRef)(null),o=(0,l.useRef)(null),[d,f]=(0,l.useState)(!0),[p,m]=(0,l.useState)(!1),[h]=a(),g=async()=>{await navigator.clipboard.writeText(e),m(!0),setTimeout(()=>m(!1),1500)};return(0,l.useEffect)(()=>{if(!r.current)return;let a=!1;return(async()=>{let{EditorView:s,EditorState:c,basicSetup:l,oneDark:u}=await i();if(a)return;let d=await n(t.split(`.`).pop()??``);if(a)return;let p=[l,c.readOnly.of(!0),s.editable.of(!1),s.theme({"&":{fontSize:`14px`},".cm-scroller":{overflow:`auto`},".cm-content":{fontFamily:`'JetBrains Mono', 'Fira Code', 'Consolas', monospace`},".cm-gutters":{fontFamily:`'JetBrains Mono', 'Fira Code', 'Consolas', monospace`},".cm-cursor":{display:`none`}})];h===`dark`&&p.push(u),d&&p.push(d),o.current=new s({state:c.create({doc:e,extensions:p}),parent:r.current}),f(!1)})(),()=>{a=!0,o.current&&=(o.current.destroy(),null)}},[e,t,h]),(0,u.jsxs)(`div`,{className:`relative group rounded-lg border border-border overflow-hidden`,children:[(0,u.jsx)(`div`,{className:`absolute top-2 right-2 z-10`,children:(0,u.jsx)(`button`,{onClick:()=>void g(),className:`p-1.5 rounded bg-accent hover:bg-accent/80 text-muted-foreground hover:text-foreground opacity-0 group-hover:opacity-100 transition-all`,title:`Copy to clipboard`,children:p?(0,u.jsx)(s,{className:`h-3.5 w-3.5 text-green-400`}):(0,u.jsx)(c,{className:`h-3.5 w-3.5`})})}),(0,u.jsx)(`div`,{ref:r,children:d&&(0,u.jsx)(`pre`,{className:`p-4 text-sm text-muted-foreground bg-muted`,children:(0,u.jsxs)(`code`,{children:[e.slice(0,200),`…`]})})})]})}export{d as CodeViewer};