@glossarist/concept-browser 0.7.62 → 0.7.64

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glossarist/concept-browser",
3
- "version": "0.7.62",
3
+ "version": "0.7.64",
4
4
  "description": "Vue SPA for browsing Glossarist terminology datasets with cross-reference resolution, graph visualization, and multi-language support",
5
5
  "type": "module",
6
6
  "bin": {
@@ -49,7 +49,8 @@ function extractReferences(concept, registerId) {
49
49
  }
50
50
 
51
51
  function extractDomains(concept, registerId, uriBase) {
52
- const base = uriBase || 'https://glossarist.org';
52
+ if (!uriBase) throw new Error('build-edges: uriBase is required — set uriBase in site-config.yml');
53
+ const base = uriBase;
53
54
  const edges = [];
54
55
  const sourceUri = concept['@id'];
55
56
  const seen = new Set();
@@ -188,7 +189,8 @@ function buildEdgesForDataset(datasetDir, registerId, uriBase, urnMap, manifest)
188
189
  // Build domain-nodes.json from manifest sections (authoritative source)
189
190
  const manifestSections = manifest.sections;
190
191
  if (manifestSections && manifestSections.length > 0) {
191
- const uriBase = manifest.uriBase || 'https://glossarist.org';
192
+ const uriBase = manifest.uriBase;
193
+ if (!uriBase) throw new Error('build-edges: manifest.uriBase is required');
192
194
 
193
195
  function buildSectionNode(section, idx) {
194
196
  const sectionId = `section-${section.id}`;
@@ -293,7 +295,8 @@ for (const ds of datasets) {
293
295
  if (!manifest) continue;
294
296
  try {
295
297
  console.log(`${manifest.title} (${ds}):`);
296
- const uriBase = manifest.uriBase || 'https://glossarist.org';
298
+ const uriBase = manifest.uriBase;
299
+ if (!uriBase) throw new Error('build-edges: manifest.uriBase is required');
297
300
  const result = buildEdgesForDataset(join(DATA_DIR, ds), ds, uriBase, urnMap, manifest);
298
301
  allDatasetEdges.set(ds, result.edges);
299
302
  allSourceRefs.push(...result.sourceRefs);
@@ -347,7 +350,8 @@ if (auditUnmatched.size > 0) {
347
350
  // edges.json contains edges targeting that dataset's URIs.
348
351
  const datasetUriPrefixes = new Map();
349
352
  for (const [ds, manifest] of manifestCache) {
350
- const uriBase = manifest.uriBase || 'https://glossarist.org';
353
+ const uriBase = manifest.uriBase;
354
+ if (!uriBase) throw new Error('build-edges: manifest.uriBase is required');
351
355
  datasetUriPrefixes.set(ds, `${uriBase}/${ds}/`);
352
356
  }
353
357
 
@@ -92,7 +92,8 @@ function writeJson(filePath, data) {
92
92
  }
93
93
 
94
94
  async function writeDatasetRdf(register, manifest, concepts, refMaps, opts) {
95
- const uriBase = refMaps?.uriBase ?? 'https://glossarist.org';
95
+ const uriBase = refMaps?.uriBase;
96
+ if (!uriBase) throw new Error('generate-data: uriBase is required — set uriBase in site-config.yml');
96
97
  const datasetIri = `${uriBase}/${register}/`;
97
98
  const topConceptUris = concepts
98
99
  .slice(0, 32)
@@ -375,7 +376,8 @@ function buildRefMaps(config, registerCache) {
375
376
  const xref = config.crossReferences || {};
376
377
  if (xref.refPrefixMap) Object.assign(refPrefixMap, xref.refPrefixMap);
377
378
 
378
- const uriBase = config.uriBase || `https://${config.domain}`;
379
+ const uriBase = config.uriBase;
380
+ if (!uriBase) throw new Error('site-config.yml: uriBase is required');
379
381
  return { patternIndex, refPrefixMap, uriBase, register: null };
380
382
  }
381
383
 
@@ -1617,7 +1619,7 @@ console.log('Emitted vocabulary graph: data/_vocab.ttl');
1617
1619
 
1618
1620
  const contributors = config.contributors ?? [];
1619
1621
  if (contributors.length > 0) {
1620
- fs.writeFileSync(path.join(DATA, 'agents.ttl'), await buildAgentsTurtle(contributors));
1622
+ fs.writeFileSync(path.join(DATA, 'agents.ttl'), await buildAgentsTurtle(contributors, refMaps.uriBase + '/agent'));
1621
1623
  console.log(`Emitted agents graph: data/agents.ttl (${contributors.length} contributors)`);
1622
1624
  }
1623
1625
 
@@ -1626,7 +1628,7 @@ for (const ds of registry) {
1626
1628
  const bibPath = path.join(DATA, ds.id, 'bibliography.json');
1627
1629
  if (fs.existsSync(bibPath)) {
1628
1630
  const bibJson = JSON.parse(fs.readFileSync(bibPath, 'utf8'));
1629
- const bibTtl = await buildBibliographyTurtle(ds.id, bibJson);
1631
+ const bibTtl = await buildBibliographyTurtle(ds.id, bibJson, refMaps.uriBase);
1630
1632
  fs.writeFileSync(path.join(DATA, ds.id, 'bib.ttl'), bibTtl);
1631
1633
  }
1632
1634
  }
@@ -19,7 +19,8 @@ function writerPrefixes() {
19
19
  };
20
20
  }
21
21
 
22
- export async function buildAgentsTurtle(contributors, agentBase = 'https://glossarist.org/agent') {
22
+ export async function buildAgentsTurtle(contributors, agentBase) {
23
+ if (!agentBase) throw new Error('buildAgentsTurtle requires agentBase');
23
24
  const agents = agentsFromContributors(contributors ?? [], agentBase);
24
25
  const quads = collectQuads(agentsToQuads(agents));
25
26
  return writeTurtle(quads, { prefixes: writerPrefixes() });
@@ -10,7 +10,7 @@ import {
10
10
  writeTurtle,
11
11
  } from 'glossarist/rdf';
12
12
 
13
- const DEFAULT_BASE_URI = 'https://glossarist.org';
13
+ // No hardcoded default base URI — callers MUST pass it.
14
14
 
15
15
  function writerPrefixes() {
16
16
  return {
@@ -22,7 +22,8 @@ function writerPrefixes() {
22
22
 
23
23
  export { normalizeBibliographyData };
24
24
 
25
- export async function buildBibliographyTurtle(register, bibliographyJson, baseUri = DEFAULT_BASE_URI) {
25
+ export async function buildBibliographyTurtle(register, bibliographyJson, baseUri) {
26
+ if (!baseUri) throw new Error('buildBibliographyTurtle requires baseUri');
26
27
  const entries = normalizeBibliographyData(bibliographyJson);
27
28
  const quads = collectQuads(bibliographyToQuads({ registerId: register, entries, baseUri }));
28
29
  return writeTurtle(quads, { prefixes: writerPrefixes() });
@@ -19,6 +19,7 @@ describe('DatasetAdapter', () => {
19
19
 
20
20
  beforeEach(() => {
21
21
  adapter = new DatasetAdapter('test', '/data/test');
22
+ adapter.manifest = { uriBase: 'https://glossarist.org' } as any;
22
23
  mockFetch.mockReset();
23
24
  });
24
25
 
@@ -0,0 +1,45 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { UriRouter } from '../adapters/UriRouter';
3
+
4
+ describe('UriRouter.parseUri — deployment-agnostic concept URI parsing', () => {
5
+ // The old parseNeighborUri in RelationSphere.vue hardcoded specific
6
+ // hostnames. UriRouter.parseUri must work for ANY deployment.
7
+ const cases: Array<[string, string, string]> = [
8
+ // [input URI, expected registerId, expected conceptId]
9
+ ['https://www.glossarist.org/iala-vocab/iala-2022/concept/1-1-000', 'iala-2022', '1-1-000'],
10
+ ['https://glossarist.org/iala-2022/concept/1-1-000', 'iala-2022', '1-1-000'],
11
+ ['https://glossarist.org/iso/concept/3.1.1', 'iso', '3.1.1'],
12
+ ['https://oimlsmart.github.io/vocab/viml-2022/concept/1.1', 'viml-2022', '1.1'],
13
+ ['https://www.oimlsmart.org/vocab/viml-2022/concept/1.1', 'viml-2022', '1.1'],
14
+ ['https://metanorma.github.io/iala-vocab/iala-2023/concept/2-1-245', 'iala-2023', '2-1-245'],
15
+ ['https://isotc204.geolexica.org/isotc204-2022/concept/3.5.8.8', 'isotc204-2022', '3.5.8.8'],
16
+ ['https://vocab.example.org/dataset-x/concept/foo', 'dataset-x', 'foo'],
17
+ ['https://custom.host/path/to/register/concept/abc-123', 'register', 'abc-123'],
18
+ ];
19
+
20
+ for (const [uri, expectedReg, expectedId] of cases) {
21
+ it(`parses ${uri}`, () => {
22
+ const parsed = UriRouter.parseUri(uri);
23
+ expect(parsed).toEqual({ registerId: expectedReg, conceptId: expectedId });
24
+ });
25
+ }
26
+
27
+ it('returns null for URIs without /concept/', () => {
28
+ expect(UriRouter.parseUri('https://example.org/foo/bar')).toBeNull();
29
+ expect(UriRouter.parseUri('urn:iso:std:iso:14812:2025:3.5.8.8')).toBeNull();
30
+ expect(UriRouter.parseUri('')).toBeNull();
31
+ });
32
+
33
+ it('does NOT hardcode any specific hostname', () => {
34
+ // Every deployment domain should work. If the regex hardcodes
35
+ // a specific host, this test would fail for others.
36
+ const diverse = [
37
+ 'https://a.io/r/concept/1',
38
+ 'https://b.com/x/y/z/concept/2',
39
+ 'https://1.2.3.4/reg/concept/3',
40
+ ];
41
+ for (const uri of diverse) {
42
+ expect(UriRouter.parseUri(uri)).not.toBeNull();
43
+ }
44
+ });
45
+ });
@@ -16,13 +16,13 @@ const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
16
16
 
17
17
  describe('buildAgentsTurtle (mjs)', () => {
18
18
  it('parses without errors', async () => {
19
- const ttl = await buildAgentsTurtle([{ name: 'Ada Lovelace' }]);
19
+ const ttl = await buildAgentsTurtle([{ name: 'Ada Lovelace' }], 'https://glossarist.org/agent');
20
20
  const store = parse(ttl);
21
21
  expect(store.size).toBeGreaterThan(0);
22
22
  });
23
23
 
24
24
  it('types each person as foaf:Person, prov:Person, prov:Agent', async () => {
25
- const ttl = await buildAgentsTurtle([{ name: 'Ada Lovelace' }]);
25
+ const ttl = await buildAgentsTurtle([{ name: 'Ada Lovelace' }], 'https://glossarist.org/agent');
26
26
  const store = parse(ttl);
27
27
  const types = store.getObjects('https://glossarist.org/agent/ada-lovelace', RDF_TYPE, null).map(q => q.value);
28
28
  expect(types).toContain(`${FOAF}Person`);
@@ -33,7 +33,7 @@ describe('buildAgentsTurtle (mjs)', () => {
33
33
  it('records name, mailto mbox, role, and seeAlso', async () => {
34
34
  const ttl = await buildAgentsTurtle([
35
35
  { name: 'Ada Lovelace', email: 'ada@example.org', role: 'Editor', url: 'https://example.org/ada' },
36
- ]);
36
+ ], 'https://glossarist.org/agent');
37
37
  const store = parse(ttl);
38
38
  const iri = 'https://glossarist.org/agent/ada-lovelace';
39
39
  expect(store.getObjects(iri, `${FOAF}name`, null).map(q => q.value)).toContain('Ada Lovelace');
@@ -46,7 +46,7 @@ describe('buildAgentsTurtle (mjs)', () => {
46
46
  const ttl = await buildAgentsTurtle([
47
47
  { name: 'Alice', organization: 'ISO' },
48
48
  { name: 'Bob', organization: 'ISO' },
49
- ]);
49
+ ], 'https://glossarist.org/agent');
50
50
  const store = parse(ttl);
51
51
  const types = store.getObjects('https://glossarist.org/org/iso', RDF_TYPE, null).map(q => q.value);
52
52
  expect(types).toContain(`${FOAF}Organization`);
@@ -54,7 +54,7 @@ describe('buildAgentsTurtle (mjs)', () => {
54
54
  });
55
55
 
56
56
  it('emits only prefix declarations for an empty input', async () => {
57
- const ttl = await buildAgentsTurtle([]);
57
+ const ttl = await buildAgentsTurtle([], 'https://glossarist.org/agent');
58
58
  expect(ttl).toContain('@prefix foaf:');
59
59
  expect(ttl).not.toContain('<https://glossarist.org/agent/');
60
60
  });
@@ -15,13 +15,13 @@ function parse(turtle: string): Store {
15
15
 
16
16
  describe('buildBibliographyTurtle (mjs)', () => {
17
17
  it('parses without errors', async () => {
18
- const ttl = await buildBibliographyTurtle('iso-geodetic', { iso704: { reference: 'ISO 704' } });
18
+ const ttl = await buildBibliographyTurtle('iso-geodetic', { iso704: { reference: 'ISO 704' } }, 'https://glossarist.org');
19
19
  const store = parse(ttl);
20
20
  expect(store.size).toBeGreaterThan(0);
21
21
  });
22
22
 
23
23
  it('types each entry as dcterms:BibliographicResource', async () => {
24
- const ttl = await buildBibliographyTurtle('iso-geodetic', { iso704: { reference: 'ISO 704' } });
24
+ const ttl = await buildBibliographyTurtle('iso-geodetic', { iso704: { reference: 'ISO 704' } }, 'https://glossarist.org');
25
25
  const store = parse(ttl);
26
26
  const types = store.getObjects('https://glossarist.org/iso-geodetic/bib/iso704', RDF_TYPE, null).map(q => q.value);
27
27
  expect(types).toContain(`${DCTERMS}BibliographicResource`);
@@ -30,7 +30,7 @@ describe('buildBibliographyTurtle (mjs)', () => {
30
30
  it('emits identifier, bibliographicCitation, and title', async () => {
31
31
  const ttl = await buildBibliographyTurtle('iso-geodetic', {
32
32
  iso704: { reference: 'ISO 704', title: 'Terminology work — Principles and methods' },
33
- });
33
+ }, 'https://glossarist.org');
34
34
  const store = parse(ttl);
35
35
  const iri = 'https://glossarist.org/iso-geodetic/bib/iso704';
36
36
  expect(store.getObjects(iri, `${DCTERMS}identifier`, null).map(q => q.value)).toContain('iso704');
@@ -41,7 +41,7 @@ describe('buildBibliographyTurtle (mjs)', () => {
41
41
  it('emits foaf:page when link is provided', async () => {
42
42
  const ttl = await buildBibliographyTurtle('iso-geodetic', {
43
43
  ref: { reference: 'X', link: 'https://example.org/x' },
44
- });
44
+ }, 'https://glossarist.org');
45
45
  const store = parse(ttl);
46
46
  const iri = 'https://glossarist.org/iso-geodetic/bib/ref';
47
47
  expect(store.getObjects(iri, `${FOAF}page`, null).map(q => q.value)).toContain('https://example.org/x');
@@ -51,7 +51,7 @@ describe('buildBibliographyTurtle (mjs)', () => {
51
51
  const ttl = await buildBibliographyTurtle('iso-geodetic', {
52
52
  a: { reference: 'A' },
53
53
  b: { reference: 'B' },
54
- });
54
+ }, 'https://glossarist.org');
55
55
  const store = parse(ttl);
56
56
  expect(store.getObjects('https://glossarist.org/iso-geodetic/bib/a', `${DCTERMS}bibliographicCitation`, null).map(q => q.value)).toContain('A');
57
57
  expect(store.getObjects('https://glossarist.org/iso-geodetic/bib/b', `${DCTERMS}bibliographicCitation`, null).map(q => q.value)).toContain('B');
@@ -61,7 +61,7 @@ describe('WS F Layer 8 — build pipeline integration', () => {
61
61
  it('agents-turtle.mjs produces parseable foaf:Person', () => {
62
62
  const out = runInline(`
63
63
  import { buildAgentsTurtle } from './scripts/lib/agents-turtle.mjs';
64
- console.log(await buildAgentsTurtle([{ name: 'Ada Lovelace', role: 'Editor', organization: 'Royal Society' }]));
64
+ console.log(await buildAgentsTurtle([{ name: 'Ada Lovelace', role: 'Editor', organization: 'Royal Society' }], 'https://glossarist.org/agent'));
65
65
  `);
66
66
  const store = parseTurtle(out);
67
67
  const persons = [...store].filter(q =>
@@ -74,7 +74,7 @@ describe('WS F Layer 8 — build pipeline integration', () => {
74
74
  it('bibliography-turtle.mjs produces parseable dcterms:BibliographicResource', () => {
75
75
  const out = runInline(`
76
76
  import { buildBibliographyTurtle } from './scripts/lib/bibliography-turtle.mjs';
77
- console.log(await buildBibliographyTurtle('test', { iso704: { reference: 'ISO 704' } }));
77
+ console.log(await buildBibliographyTurtle('test', { iso704: { reference: 'ISO 704' } }, 'https://glossarist.org'));
78
78
  `);
79
79
  const store = parseTurtle(out);
80
80
  const bibs = [...store].filter(q =>
@@ -122,8 +122,8 @@ describe('WS F Layer 8 — build pipeline integration', () => {
122
122
  await buildVocabularyTurtle(),
123
123
  await buildDatasetTurtle({ datasetIri: 'https://glossarist.org/test/', registerId: 'test', title: 't', modified: '2026-01-01', languages: ['eng'], distributions: [], topConceptUris: [], sections: [] }),
124
124
  await buildActivityTurtle({ runId: 'r', startedAt: '2026-01-01T00:00:00Z', endedAt: '2026-01-01T00:05:00Z', toolId: 'cb', toolVersion: '0', datasetRegisters: [], conceptCount: 0 }),
125
- await buildAgentsTurtle([{ name: 'Ada' }]),
126
- await buildBibliographyTurtle('test', { x: { reference: 'X' } }),
125
+ await buildAgentsTurtle([{ name: 'Ada' }], 'https://glossarist.org/agent'),
126
+ await buildBibliographyTurtle('test', { x: { reference: 'X' } }, 'https://glossarist.org'),
127
127
  await buildVersionHistoryTurtle({ registerId: 't', datasetIri: 'https://glossarist.org/t/', versions: [{ version: '1', generatedAt: '2026-01-01' }] }),
128
128
  ];
129
129
  console.log(parts.join('\\n'));
@@ -38,6 +38,7 @@ export function makeManifest(overrides: Partial<Manifest> = {}): Manifest {
38
38
 
39
39
  export interface AdapterStubOptions {
40
40
  concepts?: ConceptSummary[];
41
+ manifest?: Manifest | null;
41
42
  search?: () => SearchHit[];
42
43
  fetchConcept?: () => Promise<any>;
43
44
  getAdjacentConcepts?: () => { prev: string | null; next: string | null };
@@ -55,7 +56,7 @@ export interface AdapterStubOptions {
55
56
  export function makeAdapterStub(options: AdapterStubOptions = {}): any {
56
57
  return {
57
58
  index: options.concepts ?? [],
58
- manifest: null,
59
+ manifest: options.manifest ?? makeManifest(),
59
60
  registerId: 'test',
60
61
  getConceptCount: () => (options.concepts ?? []).length,
61
62
  getConcepts: () => options.concepts ?? [],
@@ -42,7 +42,9 @@ export class GraphDataSource {
42
42
  }
43
43
 
44
44
  private get uriBase(): string {
45
- return this.adapter.manifest?.uriBase || 'https://glossarist.org';
45
+ const uriBase = this.adapter.manifest?.uriBase;
46
+ if (!uriBase) throw new Error('GraphDataSource: manifest.uriBase is required');
47
+ return uriBase;
46
48
  }
47
49
 
48
50
  private get urnMap(): ReadonlyMap<string, string> {
@@ -18,6 +18,7 @@ import { useDsStyle } from '../utils/dataset-style';
18
18
  import { getFactory } from '../adapters/factory';
19
19
  import { SPHERE_RELATION_CATEGORIES as RELATION_CATEGORIES, sphereCategoryForType as categoryForType, categorizeRelationForSphere as categorizeRelation, colorForTypeInMode as colorForTypeRaw, relationLabel as relationTypeLabel } from '../utils/relation-sphere-styling';
20
20
  import { easeInOutCubic, slerp, fibonacciSpherePosition, project, cardEdge, type Vec3 } from '../composables/useSphereProjection';
21
+ import { UriRouter } from '../adapters/UriRouter';
21
22
  import { getPreferredTerm } from '../utils/concept-helpers';
22
23
  import { renderContent } from '../utils/content-renderer';
23
24
  import { useI18n } from '../i18n';
@@ -112,7 +113,8 @@ let navDuration = 2200; /* mutable — shorter for expand changes than for conc
112
113
  async function loadNeighborTerms() {
113
114
  const factory = getFactory();
114
115
  const lang = sphereLang.value;
115
- const uriBase = props.manifest.uriBase || 'https://glossarist.org';
116
+ const uriBase = props.manifest.uriBase;
117
+ if (!uriBase) throw new Error('RelationSphere: manifest.uriBase is required');
116
118
  const neighborRegisters = new Set<string>();
117
119
 
118
120
  /* Phase 1: collect which neighbor datasets we need to load */
@@ -226,14 +228,6 @@ function updateNodeTerm(n: SNode) {
226
228
  }
227
229
 
228
230
  /* ── Build internal graph from concept + edges (BFS) ────── */
229
- function parseNeighborUri(uri: string, fallbackReg: string): { register: string; conceptId: string } | null {
230
- const m = uri.match(/(?:glossarist\.org|oimlsmart\.github\.io\/vocab)\/([^/]+)\/concept\/([^/?#]+)/);
231
- if (m) return { register: m[1], conceptId: m[2] };
232
- /* URN or other shape — use fallback register, last path segment as id */
233
- const parts = uri.split('/').filter(Boolean);
234
- if (parts.length === 0) return null;
235
- return { register: fallbackReg, conceptId: parts[parts.length - 1] };
236
- }
237
231
 
238
232
  function hashSeed(s: string): number {
239
233
  let h = 0;
@@ -280,12 +274,12 @@ function buildGraph() {
280
274
 
281
275
  /* First pass: collect depth-1 neighbors (deduped) so we know N for even
282
276
  ring placement. */
283
- const depth1List: Array<{ uri: string; parsed: { register: string; conceptId: string }; isOutgoing: boolean; edge: GraphEdge }> = [];
277
+ const depth1List: Array<{ uri: string; parsed: { registerId: string; conceptId: string }; isOutgoing: boolean; edge: GraphEdge }> = [];
284
278
  for (const edge of props.edges) {
285
279
  const isOutgoing = edge.source === focusUri || edge.source === focusId;
286
280
  const otherUri = isOutgoing ? edge.target : edge.source;
287
281
  if (!otherUri || visited.has(otherUri)) continue;
288
- const parsed = parseNeighborUri(otherUri, edge.register || props.registerId);
282
+ const parsed = UriRouter.parseUri(otherUri);
289
283
  if (!parsed || parsed.conceptId === focusId) continue;
290
284
  visited.set(otherUri, 1);
291
285
  depth1List.push({ uri: otherUri, parsed, isOutgoing, edge });
@@ -298,8 +292,8 @@ function buildGraph() {
298
292
  nodeMap.set(parsed.conceptId, {
299
293
  id: parsed.conceptId,
300
294
  term: edge.label || parsed.conceptId,
301
- ref: parsed.register,
302
- register: parsed.register,
295
+ ref: parsed.registerId,
296
+ register: parsed.registerId,
303
297
  conceptId: parsed.conceptId,
304
298
  depth: 1,
305
299
  x: pos.x, y: pos.y, z: pos.z,
@@ -331,13 +325,13 @@ function buildGraph() {
331
325
  const inEdges = store.graph.getIncomingEdges(uri);
332
326
  /* Collect this node's children first so we can place them as an even
333
327
  sub-ring around their parent. */
334
- const children: Array<{ uri: string; parsed: { register: string; conceptId: string }; isOutgoing: boolean; edge: GraphEdge }> = [];
328
+ const children: Array<{ uri: string; parsed: { registerId: string; conceptId: string }; isOutgoing: boolean; edge: GraphEdge }> = [];
335
329
  for (const edge of [...outEdges, ...inEdges]) {
336
330
  if (nodeMap.size + children.length >= MAX_NODES) break;
337
331
  const isOutgoing = edge.source === uri;
338
332
  const otherUri = isOutgoing ? edge.target : edge.source;
339
333
  if (!otherUri || visited.has(otherUri)) continue;
340
- const parsed = parseNeighborUri(otherUri, edge.register || props.registerId);
334
+ const parsed = UriRouter.parseUri(otherUri);
341
335
  if (!parsed || parsed.conceptId === focusId || nodeMap.has(parsed.conceptId)) continue;
342
336
  visited.set(otherUri, depth + 1);
343
337
  children.push({ uri: otherUri, parsed, isOutgoing, edge });
@@ -347,8 +341,8 @@ function buildGraph() {
347
341
  nodeMap.set(parsed.conceptId, {
348
342
  id: parsed.conceptId,
349
343
  term: parsed.conceptId,
350
- ref: parsed.register,
351
- register: parsed.register,
344
+ ref: parsed.registerId,
345
+ register: parsed.registerId,
352
346
  conceptId: parsed.conceptId,
353
347
  depth: depth + 1,
354
348
  x: pos.x, y: pos.y, z: pos.z,
@@ -207,7 +207,8 @@ export const useVocabularyStore = defineStore('vocabulary', () => {
207
207
  // We do NOT call adapter.extractEdges(concept) here because that would duplicate
208
208
  // edges already present in edges.json, causing double-rendering of relations.
209
209
  const domainEdges = adapter.extractDomainEdges(concept);
210
- const uriBase = adapter.manifest?.uriBase || 'https://glossarist.org';
210
+ const uriBase = adapter.manifest?.uriBase;
211
+ if (!uriBase) throw new Error('vocabulary store: manifest.uriBase is required — set uriBase in site-config.yml');
211
212
  const uri = conceptUri(concept, registerId, uriBase);
212
213
 
213
214
  const designations: Record<string, string> = {};