@fuzdev/fuz_ui 0.194.0 → 0.195.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/DeclarationDetail.svelte +139 -64
  2. package/dist/DeclarationDetail.svelte.d.ts +9 -0
  3. package/dist/DeclarationDetail.svelte.d.ts.map +1 -1
  4. package/dist/LibraryDetail.svelte +14 -10
  5. package/dist/LibraryDetail.svelte.d.ts +7 -0
  6. package/dist/LibraryDetail.svelte.d.ts.map +1 -1
  7. package/dist/declaration.svelte.d.ts +233 -25
  8. package/dist/declaration.svelte.d.ts.map +1 -1
  9. package/dist/declaration.svelte.js +71 -23
  10. package/dist/library.svelte.js +1 -1
  11. package/dist/library_gen.d.ts +24 -17
  12. package/dist/library_gen.d.ts.map +1 -1
  13. package/dist/library_gen.js +52 -32
  14. package/dist/library_output.d.ts +5 -4
  15. package/dist/library_output.d.ts.map +1 -1
  16. package/dist/library_output.js +11 -8
  17. package/dist/module.svelte.d.ts +21 -7
  18. package/dist/module.svelte.d.ts.map +1 -1
  19. package/dist/module.svelte.js +26 -11
  20. package/dist/tsdoc_mdz.d.ts +6 -1
  21. package/dist/tsdoc_mdz.d.ts.map +1 -1
  22. package/dist/tsdoc_mdz.js +23 -2
  23. package/package.json +10 -8
  24. package/src/lib/declaration.svelte.ts +90 -35
  25. package/src/lib/library.svelte.ts +1 -1
  26. package/src/lib/library_gen.ts +65 -42
  27. package/src/lib/library_output.ts +11 -8
  28. package/src/lib/module.svelte.ts +29 -16
  29. package/src/lib/tsdoc_mdz.ts +25 -2
  30. package/dist/analysis_context.d.ts +0 -199
  31. package/dist/analysis_context.d.ts.map +0 -1
  32. package/dist/analysis_context.js +0 -138
  33. package/dist/library_analysis.d.ts +0 -112
  34. package/dist/library_analysis.d.ts.map +0 -1
  35. package/dist/library_analysis.js +0 -106
  36. package/dist/library_generate.d.ts +0 -94
  37. package/dist/library_generate.d.ts.map +0 -1
  38. package/dist/library_generate.js +0 -147
  39. package/dist/library_pipeline.d.ts +0 -113
  40. package/dist/library_pipeline.d.ts.map +0 -1
  41. package/dist/library_pipeline.js +0 -160
  42. package/dist/module_helpers.d.ts +0 -334
  43. package/dist/module_helpers.d.ts.map +0 -1
  44. package/dist/module_helpers.js +0 -317
  45. package/dist/svelte_helpers.d.ts +0 -92
  46. package/dist/svelte_helpers.d.ts.map +0 -1
  47. package/dist/svelte_helpers.js +0 -367
  48. package/dist/ts_helpers.d.ts +0 -181
  49. package/dist/ts_helpers.d.ts.map +0 -1
  50. package/dist/ts_helpers.js +0 -674
  51. package/dist/tsdoc_helpers.d.ts +0 -119
  52. package/dist/tsdoc_helpers.d.ts.map +0 -1
  53. package/dist/tsdoc_helpers.js +0 -207
  54. package/src/lib/analysis_context.ts +0 -254
  55. package/src/lib/library_analysis.ts +0 -168
  56. package/src/lib/library_generate.ts +0 -215
  57. package/src/lib/library_pipeline.ts +0 -221
  58. package/src/lib/module_helpers.ts +0 -501
  59. package/src/lib/svelte_helpers.ts +0 -539
  60. package/src/lib/ts_helpers.ts +0 -862
  61. package/src/lib/tsdoc_helpers.ts +0 -246
@@ -1,20 +1,40 @@
1
- import {
2
- type DeclarationJson,
3
- declaration_generate_import,
4
- declaration_get_display_name,
5
- } from '@fuzdev/fuz_util/source_json.js';
1
+ import type {
2
+ DeclarationJson,
3
+ DeclarationJsonInput,
4
+ MemberJsonInput,
5
+ ParameterJsonInput,
6
+ ComponentPropJsonInput,
7
+ OverloadJsonInput,
8
+ } from 'svelte-docinfo/types.js';
9
+ import {generateImport, getDisplayName} from 'svelte-docinfo/declaration-helpers.js';
6
10
 
7
11
  import type {Module} from './module.svelte.js';
8
12
  import {url_github_file} from './package_helpers.js';
9
13
 
10
- /* eslint-disable @typescript-eslint/no-deprecated */
14
+ // `library.json` is serialized with svelte-docinfo's `compactReplacer`, which
15
+ // strips empty default arrays — so the on-disk data is the `*Input` shape
16
+ // (defaulted arrays optional), not the parsed `*Json` shape. Typing it as
17
+ // input makes TypeScript force a guard on every defaulted-array read.
18
+ //
19
+ // Helper to access kind-specific fields on the discriminated union.
20
+ // At runtime the field is present or undefined; TypeScript needs the cast.
21
+ const field = <T>(decl: DeclarationJsonInput, key: string): T | undefined =>
22
+ (decl as Record<string, unknown>)[key] as T | undefined;
11
23
 
12
24
  /**
13
25
  * Rich runtime representation of an exported declaration.
26
+ *
27
+ * Wraps svelte-docinfo's `DeclarationJson` discriminated union (on `kind`)
28
+ * with Svelte 5 reactive derivations and computed URLs.
29
+ * Kind-specific fields are accessed via the `field()` helper since
30
+ * not all fields exist on all variants.
31
+ *
32
+ * @see {@link https://github.com/ryanatkn/svelte-docinfo svelte-docinfo} for the analysis library
33
+ * @see `DeclarationDetail.svelte` for the rendering component
14
34
  */
15
35
  export class Declaration {
16
36
  readonly module: Module = $state.raw()!;
17
- readonly declaration_json: DeclarationJson = $state.raw()!;
37
+ readonly declaration_json: DeclarationJsonInput = $state.raw()!;
18
38
 
19
39
  library = $derived(this.module.library);
20
40
 
@@ -30,11 +50,11 @@ export class Declaration {
30
50
  * GitHub source URL with line number.
31
51
  */
32
52
  url_github = $derived(
33
- this.library.repo_url && this.declaration_json.source_line
53
+ this.library.repo_url && this.declaration_json.sourceLine
34
54
  ? url_github_file(
35
55
  this.library.repo_url,
36
56
  `src/lib/${this.module_path}`,
37
- this.declaration_json.source_line,
57
+ this.declaration_json.sourceLine,
38
58
  )
39
59
  : undefined,
40
60
  );
@@ -48,8 +68,10 @@ export class Declaration {
48
68
  * Generated TypeScript import statement.
49
69
  */
50
70
  import_statement = $derived(
51
- declaration_generate_import(
52
- this.declaration_json,
71
+ // `generateImport` reads only `name`/`kind`/path, none of the defaulted
72
+ // arrays, so asserting the parsed shape here is sound.
73
+ generateImport(
74
+ this.declaration_json as DeclarationJson,
53
75
  this.module_path,
54
76
  this.library.package_json.name,
55
77
  ),
@@ -67,37 +89,70 @@ export class Declaration {
67
89
  /**
68
90
  * Display name with generic parameters.
69
91
  */
70
- display_name = $derived(declaration_get_display_name(this.declaration_json));
71
-
72
- type_signature = $derived(this.declaration_json.type_signature);
73
- doc_comment = $derived(this.declaration_json.doc_comment);
74
- deprecated_message = $derived(this.declaration_json.deprecated_message);
75
- parameters = $derived(this.declaration_json.parameters);
76
- props = $derived(this.declaration_json.props);
77
- return_type = $derived(this.declaration_json.return_type);
78
- return_description = $derived(this.declaration_json.return_description);
79
- generic_params = $derived(this.declaration_json.generic_params);
80
- extends = $derived(this.declaration_json.extends);
81
- implements = $derived(this.declaration_json.implements);
82
- throws = $derived(this.declaration_json.throws);
83
- since = $derived(this.declaration_json.since);
84
- examples = $derived(this.declaration_json.examples);
85
- see_also = $derived(this.declaration_json.see_also);
86
- members: Array<DeclarationJson> | undefined = $derived(
87
- this.declaration_json.members as Array<DeclarationJson> | undefined,
88
- );
89
- properties: Array<DeclarationJson> | undefined = $derived(
90
- this.declaration_json.properties as Array<DeclarationJson> | undefined,
92
+ // `getDisplayName` reads `genericParams` unguarded, so feed it the defaulted
93
+ // value rather than the raw (possibly-absent) input field.
94
+ display_name = $derived(
95
+ getDisplayName({
96
+ ...this.declaration_json,
97
+ genericParams: this.declaration_json.genericParams ?? [],
98
+ } as DeclarationJson),
91
99
  );
92
100
 
93
- has_examples = $derived(!!(this.examples && this.examples.length > 0));
101
+ type_signature = $derived(this.declaration_json.typeSignature);
102
+ doc_comment = $derived(this.declaration_json.docComment);
103
+ deprecated_message = $derived(this.declaration_json.deprecatedMessage);
104
+ parameters = $derived(field<Array<ParameterJsonInput>>(this.declaration_json, 'parameters'));
105
+ props = $derived(field<Array<ComponentPropJsonInput>>(this.declaration_json, 'props'));
106
+ return_type = $derived(field<string>(this.declaration_json, 'returnType'));
107
+ return_description = $derived(field<string>(this.declaration_json, 'returnDescription'));
108
+ generic_params = $derived(this.declaration_json.genericParams ?? []);
109
+ extends_type = $derived(field<string | Array<string>>(this.declaration_json, 'extends'));
110
+ implements_types = $derived(field<Array<string>>(this.declaration_json, 'implements'));
111
+ throws = $derived(this.declaration_json.throws ?? []);
112
+ since = $derived(this.declaration_json.since);
113
+ examples = $derived(this.declaration_json.examples ?? []);
114
+ see_also = $derived(this.declaration_json.seeAlso ?? []);
115
+ /**
116
+ * Nested members for classes, interfaces, types, and enums.
117
+ */
118
+ members = $derived(field<Array<MemberJsonInput>>(this.declaration_json, 'members'));
119
+
120
+ /**
121
+ * Intersection types whose properties are external (filtered out of props/members).
122
+ * Present on `component` and `type` kinds.
123
+ */
124
+ intersects = $derived(field<Array<string>>(this.declaration_json, 'intersects'));
125
+
126
+ /**
127
+ * Whether a component accepts children via props or template usage.
128
+ * Present on `component` kind only.
129
+ */
130
+ accepts_children = $derived(field<boolean>(this.declaration_json, 'acceptsChildren'));
131
+
132
+ /**
133
+ * Function overload signatures when multiple public overloads exist.
134
+ * Present on `function` and `snippet` kinds, and on function/constructor members.
135
+ */
136
+ overloads = $derived(field<Array<OverloadJsonInput>>(this.declaration_json, 'overloads'));
137
+
138
+ /**
139
+ * Re-export alias info when this declaration is a renamed re-export.
140
+ */
141
+ alias_of = $derived(this.declaration_json.aliasOf);
142
+
143
+ /**
144
+ * Mutation documentation from `@mutates` tags, mapping parameter names to descriptions.
145
+ */
146
+ mutates = $derived(this.declaration_json.mutates);
147
+
148
+ has_examples = $derived(this.examples.length > 0);
94
149
  is_deprecated = $derived(!!this.deprecated_message);
95
150
  has_documentation = $derived(!!this.doc_comment);
96
151
  has_parameters = $derived(!!(this.parameters && this.parameters.length > 0));
97
152
  has_props = $derived(!!(this.props && this.props.length > 0));
98
- has_generics = $derived(!!(this.generic_params && this.generic_params.length > 0));
153
+ has_generics = $derived(this.generic_params.length > 0);
99
154
 
100
- constructor(module: Module, declaration_json: DeclarationJson) {
155
+ constructor(module: Module, declaration_json: DeclarationJsonInput) {
101
156
  this.module = module;
102
157
  this.declaration_json = declaration_json;
103
158
  }
@@ -64,7 +64,7 @@ export class Library {
64
64
  */
65
65
  readonly modules = $derived(
66
66
  this.source_json.modules
67
- ? this.source_json.modules.map((module_json) => new Module(this, module_json))
67
+ ? this.source_json.modules.map((module_json) => new Module(this, module_json as any)) // TODO: remove cast when fuz_util SourceJson uses svelte-docinfo types
68
68
  : [],
69
69
  );
70
70
 
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * Gro-specific library metadata generation.
3
3
  *
4
- * This module provides Gro integration for library generation. It wraps the generic
5
- * `library_generate` function with Gro's `Gen` interface and provides adapters for
6
- * converting Gro's `Disknode` to the build-tool agnostic `SourceFileInfo`.
4
+ * This module provides Gro integration for library generation. It uses svelte-docinfo's
5
+ * pure analysis (`analyze`) and wraps the results with fuz_ui's opinionated
6
+ * LibraryJson format (GitHub/npm metadata).
7
7
  *
8
- * For build-tool agnostic usage, see `library_generate.ts`.
8
+ * For build-tool agnostic usage, see `svelte-docinfo`.
9
9
  *
10
- * @see `library_generate.ts` for the generic generation entry point
11
- * @see `library_pipeline.ts` for pipeline helpers
12
- * @see `library_output.ts` for output file generation
10
+ * @see svelte-docinfo/analyze.js for the generic analysis entry point
11
+ * @see svelte-docinfo/postprocess.js for post-processing helpers
12
+ * @see library_output.js for output file generation
13
13
  *
14
14
  * @module
15
15
  */
@@ -17,17 +17,18 @@
17
17
  import type {Gen} from '@fuzdev/gro';
18
18
  import {package_json_load} from '@fuzdev/gro/package_json.js';
19
19
  import type {Disknode} from '@fuzdev/gro/disknode.js';
20
-
21
20
  import {
22
- type SourceFileInfo,
21
+ analyze,
22
+ createSourceOptions,
23
23
  type ModuleSourceOptions,
24
- type ModuleSourcePartial,
25
- module_create_source_options,
26
- module_validate_source_options,
27
- module_is_source,
28
- module_get_source_root,
29
- } from './module_helpers.js';
30
- import {library_generate, type OnDuplicatesCallback} from './library_generate.js';
24
+ type OnDuplicatesCallback,
25
+ type SourceFileInfo,
26
+ type SourceOptionsDefaults,
27
+ } from 'svelte-docinfo';
28
+ import {normalizeSourceOptions, isSource, getSourceRoot} from 'svelte-docinfo/source-config.js';
29
+ import type {SourceJson} from '@fuzdev/fuz_util/source_json.js';
30
+
31
+ import {library_generate_output} from './library_output.js';
31
32
 
32
33
  /** Options for Gro library generation. */
33
34
  export interface LibraryGenOptions {
@@ -38,17 +39,17 @@ export interface LibraryGenOptions {
38
39
  * merged with defaults. The `project_root` is automatically set to
39
40
  * `process.cwd()` if not provided.
40
41
  */
41
- source?: ModuleSourceOptions | Partial<ModuleSourcePartial>;
42
+ source?: ModuleSourceOptions | Partial<SourceOptionsDefaults>;
42
43
  /**
43
44
  * Callback invoked when duplicate declaration names are found.
44
45
  *
45
46
  * Consumers decide how to handle duplicates: throw, warn, or ignore.
46
- * Use `library_throw_on_duplicates` for strict flat namespace enforcement.
47
+ * Use `throwOnDuplicates` for strict flat namespace enforcement.
47
48
  *
48
49
  * @example
49
50
  * ```ts
50
51
  * // Throw on duplicates (strict flat namespace)
51
- * library_gen({ on_duplicates: library_throw_on_duplicates });
52
+ * library_gen({ on_duplicates: throwOnDuplicates });
52
53
  *
53
54
  * // Warn but continue
54
55
  * library_gen({
@@ -66,7 +67,13 @@ export interface LibraryGenOptions {
66
67
  /**
67
68
  * Convert Gro's `Disknode` to the build-tool agnostic `SourceFileInfo` interface.
68
69
  *
69
- * Use this when you want to analyze files using Gro's filer directly.
70
+ * Use this when you want to analyze files using Gro's filer directly. The
71
+ * `dependencies` field is populated from the filer's forward-edge graph — the
72
+ * svelte-docinfo session honors it as pre-resolved input and skips its own
73
+ * lex+resolve pass for these files, avoiding duplicate work the filer already did.
74
+ *
75
+ * Reverse edges (`dependents`) are not threaded through — svelte-docinfo
76
+ * computes them internally from the forward edges of the owned set.
70
77
  *
71
78
  * @throws Error if disknode has no content (should be loaded by Gro filer)
72
79
  */
@@ -80,7 +87,6 @@ export const source_file_from_disknode = (disknode: Disknode): SourceFileInfo =>
80
87
  id: disknode.id,
81
88
  content: disknode.contents,
82
89
  dependencies: [...disknode.dependencies.keys()],
83
- dependents: [...disknode.dependents.keys()],
84
90
  };
85
91
  };
86
92
 
@@ -88,7 +94,7 @@ export const source_file_from_disknode = (disknode: Disknode): SourceFileInfo =>
88
94
  * Collect source files from Gro disknodes, filtering BEFORE conversion to `SourceFileInfo`.
89
95
  *
90
96
  * This avoids errors from files outside source directories (like test fixtures that may
91
- * have malformed paths or missing content). The filtering uses `module_is_source` which
97
+ * have malformed paths or missing content). The filtering uses `isSource` which
92
98
  * checks `source_paths` to only include files in configured source directories.
93
99
  *
94
100
  * @param disknodes - iterator of Gro disknodes from filer
@@ -100,8 +106,10 @@ export const library_collect_source_files_from_disknodes = (
100
106
  options: ModuleSourceOptions,
101
107
  log?: {info: (...args: Array<unknown>) => void; warn: (...args: Array<unknown>) => void},
102
108
  ): Array<SourceFileInfo> => {
103
- // Validate options early to fail fast on misconfiguration
104
- module_validate_source_options(options);
109
+ // Normalize options (throws on invalid config) and use the normalized form
110
+ // for downstream `isSource` / `getSourceRoot` calls, so callers that pass
111
+ // raw options (not via `createSourceOptions`) get consistent path handling.
112
+ const normalized_options = normalizeSourceOptions(options);
105
113
 
106
114
  const all_disknodes = Array.from(disknodes);
107
115
  log?.info(`received ${all_disknodes.length} files total from filer`);
@@ -110,7 +118,7 @@ export const library_collect_source_files_from_disknodes = (
110
118
  for (const disknode of all_disknodes) {
111
119
  // Filter by source_paths BEFORE trying to convert
112
120
  // This avoids errors from test fixtures or other non-source files
113
- if (!module_is_source(disknode.id, options)) {
121
+ if (!isSource(disknode.id, normalized_options)) {
114
122
  continue;
115
123
  }
116
124
  source_files.push(source_file_from_disknode(disknode));
@@ -119,7 +127,7 @@ export const library_collect_source_files_from_disknodes = (
119
127
  log?.info(`found ${source_files.length} source files to analyze`);
120
128
 
121
129
  if (source_files.length === 0) {
122
- const effective_root = module_get_source_root(options);
130
+ const effective_root = getSourceRoot(normalized_options);
123
131
  log?.warn(`No source files found in ${effective_root} - generating empty library metadata`);
124
132
  return [];
125
133
  }
@@ -135,10 +143,12 @@ export const library_collect_source_files_from_disknodes = (
135
143
  *
136
144
  * This is the Gro-specific entry point. It handles:
137
145
  * - Reading files from Gro's filer
138
- * - Loading `package.json` via Gro utilities
139
- * - Returning output in Gro's `Gen` format
146
+ * - Loading package.json via Gro utilities
147
+ * - Analyzing source with svelte-docinfo (pure analysis)
148
+ * - Wrapping with LibraryJson (GitHub/npm metadata)
149
+ * - Returning output in Gro's Gen format
140
150
  *
141
- * For build-tool agnostic usage, use `library_generate` directly.
151
+ * For build-tool agnostic usage, use `analyze` directly.
142
152
  *
143
153
  * Usage in a `.gen.ts` file:
144
154
  *
@@ -158,9 +168,9 @@ export const library_gen = (options?: LibraryGenOptions): Gen => {
158
168
 
159
169
  // Build source options with project_root from cwd
160
170
  const source_options: ModuleSourceOptions =
161
- options?.source && 'project_root' in options.source
171
+ options?.source && 'projectRoot' in options.source
162
172
  ? options.source
163
- : module_create_source_options(process.cwd(), options?.source);
173
+ : createSourceOptions(process.cwd(), options?.source);
164
174
 
165
175
  // Ensure filer is initialized
166
176
  await filer.init();
@@ -175,22 +185,35 @@ export const library_gen = (options?: LibraryGenOptions): Gen => {
175
185
  log,
176
186
  );
177
187
 
178
- // Use generic library_generate for the actual work
179
- const result = library_generate({
180
- source_files,
181
- package_json,
182
- source_options,
183
- on_duplicates: options?.on_duplicates,
184
- log,
188
+ // Get pure analysis from svelte-docinfo (no package metadata).
189
+ const {modules} = await analyze({
190
+ sourceFiles: source_files,
191
+ sourceOptions: source_options,
192
+ onDuplicates: options?.on_duplicates,
193
+ log: log as any, // Type cast needed due to workspace dependency duplication
185
194
  });
186
195
 
196
+ if (!package_json.version) {
197
+ throw new Error('package.json is missing required "version" field');
198
+ }
199
+ // Wrap modules with package metadata (fuz_ui's own SourceJson type)
200
+ const source_json: SourceJson = {
201
+ name: package_json.name,
202
+ version: package_json.version,
203
+ repository:
204
+ typeof package_json.repository === 'string'
205
+ ? package_json.repository
206
+ : package_json.repository?.url,
207
+ modules: modules as any, // TODO: remove cast when fuz_util SourceJson uses svelte-docinfo types
208
+ };
209
+
210
+ // Generate output files with fuz_ui's LibraryJson wrapper
211
+ const {json_content, ts_content} = library_generate_output(package_json, source_json);
212
+
187
213
  log.info('library metadata generation complete');
188
214
 
189
215
  // Return array of files in Gro's expected format
190
- return [
191
- {content: result.ts_content},
192
- {content: result.json_content, filename: 'library.json'},
193
- ];
216
+ return [{content: ts_content}, {content: json_content, filename: 'library.json'}];
194
217
  },
195
218
  };
196
219
  };
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * Library output generation.
3
3
  *
4
- * Generates the `library.json` and `library.ts` files from analyzed metadata.
4
+ * Generates the library.json and library.ts files from analyzed metadata.
5
+ * Uses svelte-docinfo's `compactReplacer` to strip Zod default values
6
+ * (empty arrays, false booleans) for compact JSON output.
5
7
  *
6
- * @see `library_generate.ts` for the main generation entry point
7
- * @see `library_pipeline.ts` for pipeline orchestration functions
8
- * @see `library_gen.ts` for Gro-specific integration
8
+ * @see `library_gen.ts` for the main generation entry point
9
+ * @see {@link https://github.com/ryanatkn/svelte-docinfo svelte-docinfo} for the analysis library
9
10
  *
10
11
  * @module
11
12
  */
@@ -13,6 +14,7 @@
13
14
  import type {PackageJson} from '@fuzdev/fuz_util/package_json.js';
14
15
  import type {SourceJson} from '@fuzdev/fuz_util/source_json.js';
15
16
  import {library_json_parse, type LibraryJson} from '@fuzdev/fuz_util/library_json.js';
17
+ import {compactReplacer} from 'svelte-docinfo';
16
18
 
17
19
  /**
18
20
  * Result of generating library output files.
@@ -39,11 +41,12 @@ export const library_generate_output = (
39
41
  package_json: PackageJson,
40
42
  source_json: SourceJson,
41
43
  ): LibraryOutputResult => {
42
- const is_this_fuz_util = package_json.name === '@fuzdev/fuz_util';
43
- const fuz_util_prefix = is_this_fuz_util ? './' : '@fuzdev/fuz_util/';
44
+ // Compact source_json (strips Zod default values like empty arrays and false booleans)
45
+ // Only applied to source_json, not the outer library_json package metadata
46
+ const compacted_source_json = JSON.parse(JSON.stringify(source_json, compactReplacer));
44
47
 
45
48
  // Parse at generation time, not runtime
46
- const library_json: LibraryJson = library_json_parse(package_json, source_json);
49
+ const library_json: LibraryJson = library_json_parse(package_json, compacted_source_json);
47
50
 
48
51
  const json_content = JSON.stringify(library_json, null, '\t') + '\n';
49
52
 
@@ -51,7 +54,7 @@ export const library_generate_output = (
51
54
 
52
55
  const ts_content = `${banner}
53
56
 
54
- import type {LibraryJson} from '${fuz_util_prefix}library_json.js';
57
+ import type {LibraryJson} from '@fuzdev/fuz_util/library_json.js';
55
58
 
56
59
  import json from './library.json' with {type: 'json'};
57
60
 
@@ -1,4 +1,4 @@
1
- import type {ModuleJson} from '@fuzdev/fuz_util/source_json.js';
1
+ import type {ModuleJsonInput} from 'svelte-docinfo/types.js';
2
2
 
3
3
  import {Declaration} from './declaration.svelte.js';
4
4
  import type {Library} from './library.svelte.js';
@@ -6,13 +6,30 @@ import {url_github_file} from './package_helpers.js';
6
6
 
7
7
  /**
8
8
  * Rich runtime representation of a module with computed properties.
9
+ *
10
+ * Wraps svelte-docinfo's `ModuleJson` with reactive derivations,
11
+ * URL generation, and `Declaration` instances.
12
+ *
13
+ * @see {@link https://github.com/ryanatkn/svelte-docinfo svelte-docinfo} for the analysis library
14
+ * @see `declaration.svelte.ts` for the `Declaration` wrapper class
9
15
  */
10
16
  export class Module {
11
17
  readonly library: Library = $state.raw()!;
12
- readonly module_json: ModuleJson = $state.raw()!;
18
+ // `library.json` is compacted (svelte-docinfo's `compactReplacer` strips empty
19
+ // default arrays), so the on-disk data is the `*Input` shape. Typing it as
20
+ // input makes TypeScript force guards on defaulted-array reads.
21
+ readonly module_json: ModuleJsonInput = $state.raw()!;
13
22
 
14
23
  /**
15
- * Canonical module path (e.g., 'Alert.ts', 'helpers/foo.ts').
24
+ * Canonical module path — `src/lib/`-relative, with source extension
25
+ * (`.ts`, `.svelte`, etc.). Examples: `'Alert.ts'`, `'helpers/foo.ts'`,
26
+ * `'actions/composables.ts'`.
27
+ *
28
+ * This is the key `Library.module_by_path` indexes, so it's also the
29
+ * exact string TSDoc backtick references must use to auto-link to a
30
+ * module via `DocsLink.svelte` (e.g., `` `actions/composables.ts` ``).
31
+ * A leading `./` or a `.js` runtime extension will not match. Top-level
32
+ * files match by bare filename; nested files require the full sub-path.
16
33
  */
17
34
  path = $derived(this.module_json.path);
18
35
 
@@ -21,17 +38,15 @@ export class Module {
21
38
  */
22
39
  path_import = $derived('./' + this.path);
23
40
 
24
- module_comment = $derived(this.module_json.module_comment);
41
+ module_comment = $derived(this.module_json.moduleComment);
25
42
 
26
43
  /**
27
44
  * Array of `Declaration` instances. Filters out default exports.
28
45
  */
29
46
  declarations = $derived(
30
- this.module_json.declarations
31
- ? this.module_json.declarations
32
- .filter((declaration_json) => declaration_json.name !== 'default')
33
- .map((declaration_json) => new Declaration(this, declaration_json))
34
- : [],
47
+ (this.module_json.declarations ?? [])
48
+ .filter((declaration_json) => declaration_json.name !== 'default')
49
+ .map((declaration_json) => new Declaration(this, declaration_json)),
35
50
  );
36
51
 
37
52
  /**
@@ -48,23 +63,21 @@ export class Module {
48
63
  : undefined,
49
64
  );
50
65
 
51
- has_declarations: boolean = $derived(
52
- !!(this.module_json.declarations && this.module_json.declarations.length > 0),
53
- );
66
+ has_declarations: boolean = $derived((this.module_json.declarations?.length ?? 0) > 0);
54
67
 
55
- has_module_comment: boolean = $derived(!!this.module_json.module_comment);
68
+ has_module_comment: boolean = $derived(!!this.module_comment);
56
69
 
57
70
  /**
58
71
  * Modules this imports (paths relative to src/lib).
59
72
  */
60
- dependencies = $derived(this.module_json.dependencies);
73
+ dependencies = $derived(this.module_json.dependencies ?? []);
61
74
 
62
75
  /**
63
76
  * Modules that import this (paths relative to src/lib).
64
77
  */
65
- dependents = $derived(this.module_json.dependents);
78
+ dependents = $derived(this.module_json.dependents ?? []);
66
79
 
67
- constructor(library: Library, module_json: ModuleJson) {
80
+ constructor(library: Library, module_json: ModuleJsonInput) {
68
81
  this.library = library;
69
82
  this.module_json = module_json;
70
83
  }
@@ -17,10 +17,12 @@ const format_reference = (ref: string): string => (mdz_is_url(ref) ? ref : `\`${
17
17
  * Convert raw TSDoc `@see` content to mdz format for rendering.
18
18
  *
19
19
  * Handles TSDoc link syntax:
20
- * - `{@link url|text}` → `[text](url)` (markdown link)
20
+ * - `{@link url|text}` → `[text](url)` (markdown link, TSDoc canonical form)
21
+ * - `{@link url text}` → `[text](url)` (TS-lenient space-separated form)
21
22
  * - `{@link https://...}` → `https://...` (bare URL, auto-linked by mdz)
22
23
  * - `{@link identifier}` → `` `identifier` `` (code formatting)
23
24
  * - Bare URLs → returned as-is
25
+ * - Bare markdown links (`[text](url)` ...) → returned as-is
24
26
  * - Bare identifiers → wrapped in backticks
25
27
  * - `identifier description text` → `` `identifier` description text `` (first token is the reference)
26
28
  *
@@ -38,6 +40,9 @@ const format_reference = (ref: string): string => (mdz_is_url(ref) ? ref : `\`${
38
40
  * tsdoc_see_to_mdz('https://example.com')
39
41
  * // → 'https://example.com'
40
42
  *
43
+ * tsdoc_see_to_mdz('[svelte-docinfo](https://github.com/ryanatkn/svelte-docinfo) for the analysis library')
44
+ * // → '[svelte-docinfo](https://github.com/ryanatkn/svelte-docinfo) for the analysis library'
45
+ *
41
46
  * tsdoc_see_to_mdz('library_gen.ts for Gro-specific integration')
42
47
  * // → '`library_gen.ts` for Gro-specific integration'
43
48
  * ```
@@ -51,7 +56,7 @@ export const tsdoc_see_to_mdz = (content: string): string => {
51
56
  if (link_match) {
52
57
  const inner = link_match[1]!.trim();
53
58
 
54
- // Check for pipe separator (custom display text)
59
+ // Pipe separator takes precedence (TSDoc canonical form)
55
60
  const pipe_index = inner.indexOf('|');
56
61
  if (pipe_index !== -1) {
57
62
  const reference = inner.slice(0, pipe_index).trim();
@@ -59,9 +64,27 @@ export const tsdoc_see_to_mdz = (content: string): string => {
59
64
  return `[${display_text}](${reference})`;
60
65
  }
61
66
 
67
+ // Space-separated form: TS accepts `{@link url text}` as equivalent to `{@link url|text}`.
68
+ // Only treat space as a separator when the first token looks like a link target (URL),
69
+ // so identifier-style references like `module.function` aren't split.
70
+ const space_index = inner.indexOf(' ');
71
+ if (space_index !== -1) {
72
+ const reference = inner.slice(0, space_index);
73
+ if (mdz_is_url(reference)) {
74
+ const display_text = inner.slice(space_index + 1).trim();
75
+ return `[${display_text}](${reference})`;
76
+ }
77
+ }
78
+
62
79
  return format_reference(inner);
63
80
  }
64
81
 
82
+ // Pass through bare markdown links (`[text](url)` optionally followed by description)
83
+ // so authors can write `@see [text](url) for context` directly.
84
+ if (trimmed.charCodeAt(0) === 91 /* [ */ && /^\[[^\]]+\]\([^)\s]+\)/.test(trimmed)) {
85
+ return trimmed;
86
+ }
87
+
65
88
  // Split at first whitespace: first token is the reference, rest is description
66
89
  const space_index = trimmed.indexOf(' ');
67
90
  if (space_index === -1) {