@glossarist/concept-browser 0.7.51 → 0.7.53

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 (159) hide show
  1. package/cli/index.mjs +32 -0
  2. package/env.d.ts +15 -0
  3. package/package.json +12 -2
  4. package/scripts/__tests__/doctor.test.mjs +147 -0
  5. package/scripts/doctor.mjs +327 -0
  6. package/scripts/generate-data.mjs +136 -0
  7. package/scripts/generate-ontology-data.mjs +3 -3
  8. package/scripts/generate-ontology-schema.mjs +3 -3
  9. package/scripts/lib/agents-turtle.mjs +64 -0
  10. package/scripts/lib/bibliography-turtle.mjs +54 -0
  11. package/scripts/lib/build-activity-turtle.mjs +92 -0
  12. package/scripts/lib/build-cache.mjs +70 -0
  13. package/scripts/lib/dataset-turtle.mjs +79 -0
  14. package/scripts/lib/turtle-escape.mjs +0 -0
  15. package/scripts/lib/version-turtle.mjs +56 -0
  16. package/scripts/lib/vocab-turtle.mjs +64 -0
  17. package/scripts/normalize-yaml.mjs +99 -0
  18. package/scripts/sync-concept-model.mjs +86 -0
  19. package/scripts/validate-shacl.mjs +194 -0
  20. package/src/App.vue +2 -0
  21. package/src/__fixtures__/concept-shape.ttl +20 -0
  22. package/src/__fixtures__/shacl/bad/concept.ttl +7 -0
  23. package/src/__fixtures__/shacl/empty/.gitkeep +0 -0
  24. package/src/__fixtures__/shacl/good/concept.ttl +8 -0
  25. package/src/__tests__/__fixtures__/concepts.ts +221 -0
  26. package/src/__tests__/adapters/concept-identity.test.ts +76 -0
  27. package/src/__tests__/components/error-boundary.test.ts +109 -0
  28. package/src/__tests__/composables/use-dataset-series.test.ts +262 -0
  29. package/src/__tests__/concept-rdf/agents-emitter.test.ts +110 -0
  30. package/src/__tests__/concept-rdf/bibliography-emitter.test.ts +159 -0
  31. package/src/__tests__/concept-rdf/build-activity-emitter.test.ts +119 -0
  32. package/src/__tests__/concept-rdf/coerce-date.test.ts +97 -0
  33. package/src/__tests__/concept-rdf/concept-emitter.test.ts +258 -0
  34. package/src/__tests__/concept-rdf/dataset-emitter.test.ts +224 -0
  35. package/src/__tests__/concept-rdf/differential.test.ts +96 -0
  36. package/src/__tests__/concept-rdf/group-emitter.test.ts +109 -0
  37. package/src/__tests__/concept-rdf/image-variant-emitter.test.ts +135 -0
  38. package/src/__tests__/concept-rdf/jsonld-writer.test.ts +109 -0
  39. package/src/__tests__/concept-rdf/nonverbal-rep.test.ts +177 -0
  40. package/src/__tests__/concept-rdf/property-based.test.ts +179 -0
  41. package/src/__tests__/concept-rdf/provenance.test.ts +110 -0
  42. package/src/__tests__/concept-rdf/quad-isomorphism.test.ts +43 -0
  43. package/src/__tests__/concept-rdf/quad-isomorphism.ts +47 -0
  44. package/src/__tests__/concept-rdf/rdf-components.test.ts +145 -0
  45. package/src/__tests__/concept-rdf/rdf-graph.test.ts +115 -0
  46. package/src/__tests__/concept-rdf/round-trip.test.ts +243 -0
  47. package/src/__tests__/concept-rdf/scoped-examples.test.ts +126 -0
  48. package/src/__tests__/concept-rdf/sections-builder.test.ts +94 -0
  49. package/src/__tests__/concept-rdf/shacl-conformance.test.ts +110 -0
  50. package/src/__tests__/concept-rdf/shape-consistency.test.ts +138 -0
  51. package/src/__tests__/concept-rdf/snapshot-generation.test.ts +75 -0
  52. package/src/__tests__/concept-rdf/table-formula-emitter.test.ts +142 -0
  53. package/src/__tests__/concept-rdf/turtle-writer.test.ts +114 -0
  54. package/src/__tests__/concept-rdf/use-rdf-document.test.ts +246 -0
  55. package/src/__tests__/concept-rdf/version-emitter.test.ts +120 -0
  56. package/src/__tests__/concept-rdf/vocabulary-ssot.test.ts +100 -0
  57. package/src/__tests__/concept-rdf-view.test.ts +136 -0
  58. package/src/__tests__/config/group-renderers.test.ts +35 -0
  59. package/src/__tests__/config/group-types.test.ts +76 -0
  60. package/src/__tests__/dataset-style.test.ts +12 -7
  61. package/src/__tests__/errors/errors.test.ts +142 -0
  62. package/src/__tests__/format-downloads.test.ts +47 -65
  63. package/src/__tests__/markdown-lite.test.ts +19 -0
  64. package/src/__tests__/perf/bundle-layout.test.ts +50 -0
  65. package/src/__tests__/perf/serialization-perf.test.ts +121 -0
  66. package/src/__tests__/scripts/agents-turtle.test.ts +61 -0
  67. package/src/__tests__/scripts/bibliography-turtle.test.ts +59 -0
  68. package/src/__tests__/scripts/build-activity-turtle.test.ts +75 -0
  69. package/src/__tests__/scripts/build-cache.test.ts +78 -0
  70. package/src/__tests__/scripts/build-integration.test.ts +134 -0
  71. package/src/__tests__/scripts/dataset-turtle.test.ts +94 -0
  72. package/src/__tests__/scripts/normalize-yaml.test.ts +98 -0
  73. package/src/__tests__/scripts/stryker-config.test.ts +33 -0
  74. package/src/__tests__/scripts/turtle-escape.test.ts +63 -0
  75. package/src/__tests__/scripts/version-turtle.test.ts +72 -0
  76. package/src/__tests__/scripts/vocab-turtle.test.ts +63 -0
  77. package/src/__tests__/use-format-registry.test.ts +125 -0
  78. package/src/__tests__/utils/bcp47.test.ts +166 -0
  79. package/src/__tests__/utils/color-theme.test.ts +143 -0
  80. package/src/__tests__/utils/url-safety.test.ts +65 -0
  81. package/src/__tests__/validate-shacl.test.ts +100 -0
  82. package/src/adapters/DatasetAdapter.ts +11 -5
  83. package/src/adapters/GraphDataSource.ts +2 -1
  84. package/src/adapters/UriRouter.ts +2 -1
  85. package/src/adapters/concept-identity.ts +69 -0
  86. package/src/adapters/factory.ts +3 -2
  87. package/src/adapters/model-bridge.ts +2 -1
  88. package/src/adapters/non-verbal/glossarist-augment.d.ts +7 -0
  89. package/src/adapters/non-verbal-resolver.ts +2 -1
  90. package/src/components/AppSidebar.vue +189 -93
  91. package/src/components/ConceptDetail.vue +8 -0
  92. package/src/components/ConceptEditionRail.vue +222 -0
  93. package/src/components/ConceptRdfView.vue +37 -377
  94. package/src/components/DatasetSeriesCard.vue +270 -0
  95. package/src/components/ErrorBoundary.vue +95 -0
  96. package/src/components/FormatDownloads.vue +17 -13
  97. package/src/components/HomeSeriesSection.vue +277 -0
  98. package/src/components/RelationSphere.vue +1672 -0
  99. package/src/components/SidebarSeriesSection.vue +239 -0
  100. package/src/components/concept-rdf/RdfInstanceHeader.vue +47 -0
  101. package/src/components/concept-rdf/RdfInstanceSection.vue +54 -0
  102. package/src/components/concept-rdf/RdfPrefixLegend.vue +27 -0
  103. package/src/components/concept-rdf/RdfSourcePanel.vue +72 -0
  104. package/src/components/concept-rdf/agents-emitter.ts +82 -0
  105. package/src/components/concept-rdf/bibliography-emitter.ts +83 -0
  106. package/src/components/concept-rdf/build-activity-emitter.ts +89 -0
  107. package/src/components/concept-rdf/concept-emitter.ts +443 -0
  108. package/src/components/concept-rdf/dataset-emitter.ts +95 -0
  109. package/src/components/concept-rdf/group-emitter.ts +69 -0
  110. package/src/components/concept-rdf/image-variant-emitter.ts +46 -0
  111. package/src/components/concept-rdf/jsonld-writer.ts +82 -0
  112. package/src/components/concept-rdf/predicates.ts +261 -0
  113. package/src/components/concept-rdf/provenance.ts +80 -0
  114. package/src/components/concept-rdf/rdf-graph.ts +211 -0
  115. package/src/components/concept-rdf/rdf-prefixes.ts +23 -0
  116. package/src/components/concept-rdf/sections-builder.ts +62 -0
  117. package/src/components/concept-rdf/table-formula-emitter.ts +101 -0
  118. package/src/components/concept-rdf/turtle-writer.ts +116 -0
  119. package/src/components/concept-rdf/use-rdf-document.ts +72 -0
  120. package/src/components/concept-rdf/version-emitter.ts +65 -0
  121. package/src/components/concept-rdf/vocabulary-emitter.ts +62 -0
  122. package/src/components/groups/DatasetGroupRenderer.vue +32 -0
  123. package/src/components/groups/DefaultGroupSidebar.vue +50 -0
  124. package/src/components/groups/LineageGroupSidebar.vue +75 -0
  125. package/src/composables/use-color-theme.ts +82 -0
  126. package/src/composables/use-format-registry.ts +42 -0
  127. package/src/composables/useDatasetSeries.ts +258 -0
  128. package/src/composables/useSphereProjection.ts +125 -0
  129. package/src/config/group-renderers.ts +27 -0
  130. package/src/config/group-types.ts +92 -0
  131. package/src/config/types.ts +81 -2
  132. package/src/config/use-site-config.ts +2 -1
  133. package/src/errors.ts +136 -0
  134. package/src/i18n/locales/eng.yml +24 -0
  135. package/src/i18n/locales/fra.yml +24 -0
  136. package/src/stores/vocabulary.ts +3 -1
  137. package/src/style.css +17 -2
  138. package/src/types/agents-version-turtle.d.ts +27 -0
  139. package/src/types/bibliography-turtle.d.ts +12 -0
  140. package/src/types/build-activity-turtle.d.ts +16 -0
  141. package/src/types/build-cache.d.ts +20 -0
  142. package/src/types/dataset-turtle.d.ts +32 -0
  143. package/src/types/normalize-yaml.d.ts +16 -0
  144. package/src/types/turtle-escape.d.ts +6 -0
  145. package/src/types/vocab-turtle.d.ts +13 -0
  146. package/src/utils/asciidoc-lite.ts +11 -6
  147. package/src/utils/bcp47.ts +141 -0
  148. package/src/utils/color-theme-integration.ts +11 -0
  149. package/src/utils/color-theme.ts +129 -0
  150. package/src/utils/dataset-style.ts +31 -6
  151. package/src/utils/locale.ts +6 -14
  152. package/src/utils/markdown-lite.ts +6 -1
  153. package/src/utils/relation-sphere-styling.ts +63 -0
  154. package/src/utils/relationship-categories.ts +30 -0
  155. package/src/utils/url-safety.ts +30 -0
  156. package/src/views/ConceptView.vue +187 -9
  157. package/src/views/DatasetView.vue +6 -0
  158. package/src/views/HomeView.vue +5 -0
  159. package/vite.config.ts +7 -0
@@ -82,6 +82,8 @@ export interface RoutingEntry {
82
82
 
83
83
  // === Dataset ===
84
84
 
85
+ export type DatasetColorSpec = string | { light: string; dark: string };
86
+
85
87
  export interface DatasetConfig {
86
88
  id: string;
87
89
  uri: string;
@@ -92,7 +94,13 @@ export interface DatasetConfig {
92
94
  title: string;
93
95
  description?: string;
94
96
  owner?: string;
95
- color?: string;
97
+ /**
98
+ * Dataset accent color. Accepts either a single hex (applied to both
99
+ * light and dark modes) or an explicit `{ light, dark }` pair.
100
+ * Per-deployment overrides via `site-config.json` `colors.dataset[id]`
101
+ * take precedence.
102
+ */
103
+ color?: DatasetColorSpec;
96
104
  tags?: string[];
97
105
  languageOrder?: string[];
98
106
  ref?: string;
@@ -142,17 +150,86 @@ export interface PageConfig {
142
150
 
143
151
  // === Dataset Groups ===
144
152
 
153
+ /**
154
+ * Kind of dataset group. Determines how the group is rendered in the sidebar
155
+ * and home page, and what semantic relationships between members are assumed.
156
+ *
157
+ * - `lineage` — same vocabulary, different editions (e.g. VIML 1968/2000/2013/2022).
158
+ * Members have temporal ordering and a supersession chain. Rendered as a
159
+ * timeline with year badges and "current" markers.
160
+ *
161
+ * - `topic` — different vocabularies on the same subject (e.g. three SDOs
162
+ * publishing "intelligent transport systems" terminology). Members may
163
+ * overlap in concepts but have no temporal ordering. Rendered as a card
164
+ * grid with overlap indicators.
165
+ *
166
+ * - `family` — related vocabularies from the same publisher or program
167
+ * (e.g. all OIML publications). Hierarchical grouping, no required
168
+ * relationships between members. Rendered as a flat list under a labeled
169
+ * header.
170
+ *
171
+ * - `collection` — curated bundle of datasets (e.g. "Starter pack for new
172
+ * metrologists"). Arbitrary selection, often cross-publisher. Rendered as
173
+ * a featured card with custom descriptions.
174
+ *
175
+ * - `default` (omitted) — backward-compatible flat list. No special
176
+ * semantics. Used when no `kind` is specified.
177
+ *
178
+ * The registry in `src/config/group-types.ts` maps each kind to its renderer
179
+ * component, so new kinds can be added without modifying existing code
180
+ * (open/closed principle).
181
+ */
182
+ export type DatasetGroupKind = 'lineage' | 'topic' | 'family' | 'collection' | 'default';
183
+
145
184
  export interface DatasetGroup {
146
185
  id: string;
147
186
  label: string;
148
187
  description?: string;
149
- color?: string;
188
+ /**
189
+ * Group accent color. Same shape as DatasetConfig.color.
190
+ * Per-deployment overrides via `site-config.json` `colors.group[id]`.
191
+ */
192
+ color?: DatasetColorSpec;
150
193
  datasets: string[];
151
194
  translations?: Record<string, { label?: string; description?: string }>;
195
+ /**
196
+ * Discriminator for the group's semantic type and UX. See DatasetGroupKind
197
+ * for the full list of supported values. Defaults to 'default' (flat list).
198
+ *
199
+ * Replaces the older `series?: boolean` flag — use `kind: lineage` instead.
200
+ */
201
+ kind?: DatasetGroupKind;
202
+ /**
203
+ * For lineage series: the dataset id of the current (newest valid) edition.
204
+ * If omitted, the newest member by year (or last in `datasets` order) is
205
+ * used. Setting this explicitly avoids misdetection when only a subset of
206
+ * editions happen to be loaded.
207
+ */
208
+ current?: string;
209
+ /**
210
+ * @deprecated Use `kind: 'lineage'` instead. Still respected as a
211
+ * backward-compat shorthand: `series: true` is treated as `kind: 'lineage'`.
212
+ */
213
+ series?: boolean;
152
214
  }
153
215
 
154
216
  // === Site Config ===
155
217
 
218
+ export interface SiteColors {
219
+ /** Per-dataset color overrides. Keyed by dataset id. */
220
+ dataset?: Record<string, DatasetColorSpec>;
221
+ /** Per-group color overrides. Keyed by group id. */
222
+ group?: Record<string, DatasetColorSpec>;
223
+ /** Per-relation-type color overrides. Keyed by type id (e.g. "supersedes"). */
224
+ relationshipType?: Record<string, DatasetColorSpec>;
225
+ /** Per-relation-category color overrides. Keyed by category id (e.g. "lifecycle"). */
226
+ relationshipCategory?: Record<string, DatasetColorSpec>;
227
+ /** Per-concept-status color overrides. Keyed by status id. */
228
+ conceptStatus?: Record<string, DatasetColorSpec>;
229
+ /** Per-group-kind color overrides. Keyed by DatasetGroupKind. */
230
+ groupKind?: Record<string, DatasetColorSpec>;
231
+ }
232
+
156
233
  export interface SiteConfig {
157
234
  id: string;
158
235
  domain: string;
@@ -171,6 +248,8 @@ export interface SiteConfig {
171
248
  social?: SocialLinks;
172
249
  nav?: NavItem[];
173
250
  footerNav?: NavItem[];
251
+ /** Color overrides. Merged over `data/colors.json` defaults. */
252
+ colors?: SiteColors;
174
253
  defaults: {
175
254
  language?: string;
176
255
  languageOrder?: string[];
@@ -1,5 +1,5 @@
1
1
  import { ref, computed } from 'vue';
2
- import type { PageConfig } from './types';
2
+ import type { PageConfig, SiteColors } from './types';
3
3
  import type { DatasetGroup } from './types';
4
4
  import { locale } from '../i18n';
5
5
 
@@ -38,6 +38,7 @@ export interface RuntimeSiteConfig {
38
38
  pages?: PageConfig[];
39
39
  contributors?: { name: string; role?: string; organization?: string; url?: string; email?: string }[];
40
40
  copyright?: string;
41
+ colors?: SiteColors;
41
42
  }
42
43
 
43
44
  const siteConfig = ref<RuntimeSiteConfig | null>(null);
package/src/errors.ts ADDED
@@ -0,0 +1,136 @@
1
+ export interface ErrorContext {
2
+ readonly conceptId?: string;
3
+ readonly registerId?: string;
4
+ readonly locale?: string;
5
+ readonly predicate?: string;
6
+ readonly sourcePath?: string;
7
+ readonly cause?: unknown;
8
+ readonly [key: string]: unknown;
9
+ }
10
+
11
+ export class GlossaristError extends Error {
12
+ constructor(
13
+ message: string,
14
+ public readonly context: ErrorContext = {},
15
+ ) {
16
+ super(message);
17
+ this.name = this.constructor.name;
18
+ }
19
+
20
+ hint(): string {
21
+ return '';
22
+ }
23
+ }
24
+
25
+ export class ConfigurationError extends GlossaristError {}
26
+ export class DataSourceError extends GlossaristError {}
27
+ export class SerializationError extends GlossaristError {}
28
+ export class ValidationError extends GlossaristError {}
29
+
30
+ export class UnknownDatasetError extends ConfigurationError {
31
+ static make(registerId: string): UnknownDatasetError {
32
+ return new UnknownDatasetError(`Unknown dataset: ${registerId}`, { registerId });
33
+ }
34
+
35
+ hint(): string {
36
+ return 'Check datasets.yml / datasets.json registration and adapter discovery.';
37
+ }
38
+ }
39
+
40
+ export class DatasetRegistryLoadError extends DataSourceError {
41
+ static make(status: number, url?: string): DatasetRegistryLoadError {
42
+ return new DatasetRegistryLoadError(
43
+ `Failed to load dataset registry: HTTP ${status}`,
44
+ { status, url },
45
+ );
46
+ }
47
+ }
48
+
49
+ export class ManifestLoadError extends DataSourceError {
50
+ static make(registerId: string, status: number): ManifestLoadError {
51
+ return new ManifestLoadError(
52
+ `Failed to load manifest for ${registerId}: HTTP ${status}`,
53
+ { registerId, status },
54
+ );
55
+ }
56
+ }
57
+
58
+ export class IndexLoadError extends DataSourceError {
59
+ static make(registerId: string, status: number): IndexLoadError {
60
+ return new IndexLoadError(
61
+ `Failed to load index for ${registerId}: HTTP ${status}`,
62
+ { registerId, status },
63
+ );
64
+ }
65
+ }
66
+
67
+ export class ChunkLoadError extends DataSourceError {
68
+ static make(registerId: string, chunkIndex: number, status: number): ChunkLoadError {
69
+ return new ChunkLoadError(
70
+ `Failed to load chunk ${chunkIndex} for ${registerId}: HTTP ${status}`,
71
+ { registerId, chunkIndex, status },
72
+ );
73
+ }
74
+ }
75
+
76
+ export class ConceptNotFoundError extends DataSourceError {
77
+ static make(registerId: string, conceptId: string): ConceptNotFoundError {
78
+ return new ConceptNotFoundError(
79
+ `Concept ${conceptId} not found in ${registerId}`,
80
+ { registerId, conceptId },
81
+ );
82
+ }
83
+ }
84
+
85
+ export class NonVerbalEntityNotFoundError extends DataSourceError {
86
+ static make(datasetId: string, kind: string, entityId: string, status: number): NonVerbalEntityNotFoundError {
87
+ return new NonVerbalEntityNotFoundError(
88
+ `Failed to load ${kind} ${entityId} from ${datasetId}: HTTP ${status}`,
89
+ { datasetId, kind, entityId, status },
90
+ );
91
+ }
92
+ }
93
+
94
+ export class InvalidConceptIdentityError extends SerializationError {
95
+ hint(): string {
96
+ return 'ConceptIdentity requires non-empty localId, registerId, and uriBase.';
97
+ }
98
+ }
99
+
100
+ export class InvalidConceptUriError extends SerializationError {
101
+ static make(uri: string): InvalidConceptUriError {
102
+ return new InvalidConceptUriError(`Not a concept URI: ${uri}`, { uri });
103
+ }
104
+
105
+ hint(): string {
106
+ return 'Expected format: <uriBase>/<registerId>/concept/<conceptId>';
107
+ }
108
+ }
109
+
110
+ export class InvalidLangTagError extends SerializationError {
111
+ hint(): string {
112
+ return 'Use BCP-47 form: primary[-script][-region][-variant]*[-x-private]. ' +
113
+ 'ISO 639-3 codes (eng, fra) are normalized to ISO 639-1 (en, fr).';
114
+ }
115
+ }
116
+
117
+ export function isGlossaristError(err: unknown): err is GlossaristError {
118
+ return err instanceof GlossaristError;
119
+ }
120
+
121
+ export function formatError(err: GlossaristError): string {
122
+ const lines = [`${err.name}: ${err.message}`];
123
+ const fields: Record<string, unknown> = {};
124
+ for (const [k, v] of Object.entries(err.context)) {
125
+ if (k === 'cause' || v === undefined || v === null) continue;
126
+ fields[k] = v;
127
+ }
128
+ if (Object.keys(fields).length) {
129
+ for (const [k, v] of Object.entries(fields)) {
130
+ lines.push(` ${k}: ${typeof v === 'object' ? JSON.stringify(v) : v}`);
131
+ }
132
+ }
133
+ const h = err.hint();
134
+ if (h) lines.push(` hint: ${h}`);
135
+ return lines.join('\n');
136
+ }
@@ -97,6 +97,12 @@ concept.failedToLoad: Failed to load concept
97
97
  concept.notFound: Concept not found
98
98
  concept.notFoundMsg: "The concept \"{id}\" does not exist in this dataset."
99
99
  concept.backToDataset: Back to dataset
100
+ concept.editionSeries: Edition series
101
+ concept.viewing: viewing
102
+ concept.currentEdition: current edition
103
+ edge.supersedes: supersedes
104
+ edge.superseded_by: superseded by
105
+ concept.noChain: No supersession chain recorded for this concept.
100
106
 
101
107
  # Sidebar
102
108
  sidebar.overview: Overview
@@ -185,3 +191,21 @@ dataset.alphabetical: Alphabetical
185
191
  dataset.sectionFilter: Section
186
192
  dataset.clearSection: Clear section
187
193
  dataset.conceptsInSection: concepts in section
194
+
195
+ # Relation sphere view
196
+ sphere.viewOptions: View options
197
+ sphere.degree: Degree
198
+ sphere.language: Language
199
+ sphere.datasets: Datasets
200
+ sphere.relationTypes: Relation types
201
+ sphere.expand: Expand
202
+ sphere.tight: tight
203
+ sphere.loose: loose
204
+ sphere.redraw: Redraw
205
+ sphere.focus: FOCUS
206
+ concept.detailView: Detail
207
+ concept.relationSphere: Relation Sphere
208
+ concept.permalink: Permalink
209
+ concept.copied: Copied
210
+
211
+ # Relation types (displayed in legend + edge labels)
@@ -97,6 +97,12 @@ concept.failedToLoad: Échec du chargement du concept
97
97
  concept.notFound: Concept non trouvé
98
98
  concept.notFoundMsg: "Le concept « {id} » n'existe pas dans ce jeu de données."
99
99
  concept.backToDataset: Retour au jeu de données
100
+ concept.editionSeries: Série d'éditions
101
+ concept.viewing: affiché
102
+ concept.currentEdition: édition courante
103
+ edge.supersedes: remplace
104
+ edge.superseded_by: remplacé par
105
+ concept.noChain: Aucune chaîne de succession enregistrée pour ce concept.
100
106
 
101
107
  # Sidebar
102
108
  sidebar.overview: Vue d'ensemble
@@ -185,3 +191,21 @@ dataset.alphabetical: Alphabétique
185
191
  dataset.sectionFilter: Section
186
192
  dataset.clearSection: Effacer la section
187
193
  dataset.conceptsInSection: concepts dans la section
194
+
195
+ # Relation sphere view
196
+ sphere.viewOptions: Options d'affichage
197
+ sphere.degree: Degré
198
+ sphere.language: Langue
199
+ sphere.datasets: Jeux de données
200
+ sphere.relationTypes: Types de relation
201
+ sphere.expand: Expansion
202
+ sphere.tight: serré
203
+ sphere.loose: étendu
204
+ sphere.redraw: Redessiner
205
+ sphere.focus: FOCUS
206
+ concept.detailView: Détail
207
+ concept.relationSphere: Sphère de relations
208
+ concept.permalink: Permalien
209
+ concept.copied: Copié
210
+
211
+ # Relation types (affichés dans la légende + étiquettes d'arêtes)
@@ -8,6 +8,7 @@ import { conceptUri } from '../adapters/model-bridge';
8
8
  import { GraphEngine } from '../graph';
9
9
  import { UriRouter } from '../adapters/UriRouter';
10
10
  import { deduplicateSearchHits } from '../utils/search';
11
+ import { UnknownDatasetError } from '../errors';
11
12
 
12
13
  export const useVocabularyStore = defineStore('vocabulary', () => {
13
14
  // State
@@ -193,7 +194,7 @@ export const useVocabularyStore = defineStore('vocabulary', () => {
193
194
 
194
195
  try {
195
196
  const adapter = datasets.value.get(registerId);
196
- if (!adapter) throw new Error(`Dataset ${registerId} not loaded`);
197
+ if (!adapter) throw UnknownDatasetError.make(registerId);
197
198
 
198
199
  // Fetch concept and cross-dataset edges in parallel
199
200
  const [concept] = await Promise.all([
@@ -332,6 +333,7 @@ export const useVocabularyStore = defineStore('vocabulary', () => {
332
333
  datasetList,
333
334
  discoverDatasets,
334
335
  loadDataset,
336
+ ensureEdgesForDataset,
335
337
  viewConcept,
336
338
  navigateToUri,
337
339
  searchAcrossDatasets,
package/src/style.css CHANGED
@@ -482,10 +482,25 @@
482
482
 
483
483
  /* ── Sidebar group labels (brand colors are too dark in dark mode) ── */
484
484
  .dark .sidebar-group-label {
485
- filter: brightness(2);
485
+ filter: brightness(1.6);
486
486
  }
487
487
  .dark .sidebar-group-label:hover {
488
- filter: brightness(1.8);
488
+ filter: brightness(1.4);
489
+ }
490
+
491
+ /* ── Gold accent that adapts to theme ── */
492
+ :root {
493
+ --gold-accent: #B8935A;
494
+ --gold-accent-deep: #8C6A3A;
495
+ }
496
+ .dark {
497
+ --gold-accent: #D4AF6E;
498
+ --gold-accent-deep: #B8935A;
499
+ }
500
+
501
+ /* Section labels brighter in dark mode */
502
+ .dark .section-label {
503
+ color: theme('colors.ink.300') !important;
489
504
  }
490
505
 
491
506
  /* Scrollbar hide utility */
@@ -0,0 +1,27 @@
1
+ declare module '*/scripts/lib/agents-turtle.mjs' {
2
+ export function buildAgentsTurtle(
3
+ contributors: readonly { name: string; role?: string; organization?: string; url?: string; email?: string }[],
4
+ agentBase?: string,
5
+ ): string;
6
+ }
7
+
8
+ declare module '*/scripts/lib/version-turtle.mjs' {
9
+ export interface VersionInput {
10
+ readonly registerId: string;
11
+ readonly version: string;
12
+ readonly versionIri: string;
13
+ readonly datasetIri: string;
14
+ readonly generatedAt: string;
15
+ readonly previousVersionIri?: string;
16
+ readonly changeSummary?: string;
17
+ readonly associatedAgentIri?: string;
18
+ }
19
+ export interface VersionHistoryInput {
20
+ readonly registerId: string;
21
+ readonly datasetIri: string;
22
+ readonly versions: readonly { version: string; generatedAt: string; changeSummary?: string }[];
23
+ readonly associatedAgentIri?: string;
24
+ }
25
+ export function buildVersionTurtle(input: VersionInput): string;
26
+ export function buildVersionHistoryTurtle(input: VersionHistoryInput): string;
27
+ }
@@ -0,0 +1,12 @@
1
+ declare module '*/scripts/lib/bibliography-turtle.mjs' {
2
+ export interface BibliographyEntryJson {
3
+ readonly reference?: string;
4
+ readonly title?: string;
5
+ readonly link?: string;
6
+ }
7
+ export function buildBibliographyTurtle(
8
+ register: string,
9
+ bibliographyJson: Record<string, BibliographyEntryJson>,
10
+ baseUri?: string,
11
+ ): string;
12
+ }
@@ -0,0 +1,16 @@
1
+ declare module '*/scripts/lib/build-activity-turtle.mjs' {
2
+ export interface BuildActivityTurtleInput {
3
+ readonly runId: string;
4
+ readonly startedAt: string;
5
+ readonly endedAt: string;
6
+ readonly gitSha?: string | null;
7
+ readonly gitBranch?: string | null;
8
+ readonly toolId: string;
9
+ readonly toolVersion: string;
10
+ readonly datasetRegisters: readonly string[];
11
+ readonly conceptCount: number;
12
+ readonly associatedAgentIri?: string | null;
13
+ }
14
+
15
+ export function buildActivityTurtle(input: BuildActivityTurtleInput): string;
16
+ }
@@ -0,0 +1,20 @@
1
+ declare module '*/scripts/lib/build-cache.mjs' {
2
+ export interface BuildCacheEntry<T = unknown> {
3
+ hash: string;
4
+ value: T;
5
+ storedAt: string;
6
+ }
7
+
8
+ export class BuildCache {
9
+ constructor(cacheDir: string);
10
+ get<T = unknown>(key: string): Promise<BuildCacheEntry<T> | null>;
11
+ set<T = unknown>(key: string, value: T, hash: string): Promise<void>;
12
+ readThrough<T>(
13
+ key: string,
14
+ hash: string,
15
+ producer: () => Promise<T>,
16
+ ): Promise<{ value: T; hit: boolean }>;
17
+ pathFor(key: string): string;
18
+ static hash(input: string): string;
19
+ }
20
+ }
@@ -0,0 +1,32 @@
1
+ declare module '*/scripts/lib/dataset-turtle.mjs' {
2
+ export interface DatasetDistribution {
3
+ readonly id: string;
4
+ readonly title: string;
5
+ readonly mediaType: string;
6
+ readonly downloadUrl: string;
7
+ readonly byteSize?: number;
8
+ }
9
+
10
+ export interface DatasetSection {
11
+ readonly collectionIri: string;
12
+ readonly title: string;
13
+ readonly memberUris: readonly string[];
14
+ }
15
+
16
+ export interface DatasetTurtleInput {
17
+ readonly datasetIri: string;
18
+ readonly registerId: string;
19
+ readonly title: string;
20
+ readonly description?: string;
21
+ readonly modified: string;
22
+ readonly languages: readonly string[];
23
+ readonly distributions: readonly DatasetDistribution[];
24
+ readonly topConceptUris: readonly string[];
25
+ readonly sections: readonly DatasetSection[];
26
+ readonly sourceRepoUrl?: string;
27
+ readonly publisherIri?: string;
28
+ readonly contactIri?: string;
29
+ }
30
+
31
+ export function buildDatasetTurtle(input: DatasetTurtleInput): string;
32
+ }
@@ -0,0 +1,16 @@
1
+ declare module '*/scripts/normalize-yaml.mjs' {
2
+ export interface NormalizeResult {
3
+ checked: number;
4
+ nonNfc: number;
5
+ fixed: string[];
6
+ check: boolean;
7
+ }
8
+
9
+ export interface NormalizeOptions {
10
+ root?: string;
11
+ check?: boolean;
12
+ paths?: string[];
13
+ }
14
+
15
+ export function normalizeYaml(options?: NormalizeOptions): NormalizeResult;
16
+ }
@@ -0,0 +1,6 @@
1
+ declare module '*/scripts/lib/turtle-escape.mjs' {
2
+ export function ttlLit(s: unknown): string;
3
+ export function ttlPrefixed(qname: string): string;
4
+ export function ttlIri(iri: string): string;
5
+ export function assertValidIri(iri: unknown, context?: string): string;
6
+ }
@@ -0,0 +1,13 @@
1
+ declare module '*/scripts/lib/vocab-turtle.mjs' {
2
+ export interface VocabTerm {
3
+ readonly iri: string;
4
+ readonly label: string;
5
+ }
6
+ export interface VocabScheme {
7
+ readonly schemeIri: string;
8
+ readonly label: string;
9
+ readonly terms: readonly VocabTerm[];
10
+ }
11
+ export function buildVocabularyTurtle(): string;
12
+ export function listVocabSchemes(): readonly VocabScheme[];
13
+ }
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { escapeHtml, escapeAttr } from './escape';
7
+ import { sanitizeUrl } from './url-safety';
7
8
 
8
9
  export function renderAsciiDocLite(text: string): string {
9
10
  if (!text) return '';
@@ -102,14 +103,18 @@ export function renderAsciiDocLite(text: string): string {
102
103
 
103
104
  function inlineFormat(text: string): string {
104
105
  // AsciiDoc link: https://example.com[text]
105
- text = text.replace(/(https?:\/\/[^\s\[]+)\[([^\]]*)\]/g, (_, url, label) =>
106
- `<a href="${escapeHtml(url)}" target="_blank" rel="noopener">${escapeHtml(label || url)}</a>`
107
- );
106
+ text = text.replace(/(https?:\/\/[^\s\[]+)\[([^\]]*)\]/g, (_, url, label) => {
107
+ const href = sanitizeUrl(url);
108
+ if (!href) return escapeHtml(label || url);
109
+ return `<a href="${escapeHtml(href)}" target="_blank" rel="noopener">${escapeHtml(label || url)}</a>`;
110
+ });
108
111
 
109
112
  // Bare URLs
110
- text = text.replace(/(?<!href="|">)(https?:\/\/[^\s<]+)/g, url =>
111
- `<a href="${escapeHtml(url)}" target="_blank" rel="noopener">${escapeHtml(url)}</a>`
112
- );
113
+ text = text.replace(/(?<!href="|">)(https?:\/\/[^\s<]+)/g, url => {
114
+ const href = sanitizeUrl(url);
115
+ if (!href) return escapeHtml(url);
116
+ return `<a href="${escapeHtml(href)}" target="_blank" rel="noopener">${escapeHtml(url)}</a>`;
117
+ });
113
118
 
114
119
  // Monospace: `text`
115
120
  text = text.replace(/`([^`]+)`/g, '<code>$1</code>');