@glossarist/concept-browser 0.7.84 → 0.7.86

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.
@@ -1,23 +1,28 @@
1
1
  /**
2
- * Partitive relation styling — colors for one-to-many decompositions.
2
+ * Partitive relation styling — colors and labels for one-to-many
3
+ * partitive decompositions.
3
4
  *
4
5
  * Independent of relationship-categories.ts (which handles binary edges).
5
- * Partitive relations have their own visual language:
6
- * - completeness (complete/partial) affects opacity
7
- * - plurality (type-shared, uncertain) adds accent badges
6
+ * The visual encoding for PartitiveRelation comes from two orthogonal
7
+ * axes per ISO 704:2022:
8
8
  *
9
- * v2 redesign: replaces v1 hyperedge-styling.ts per
10
- * concept-model/TODO.partitive-relation-v2.
9
+ * 1. completeness (complete / partial) — relation-level metadata,
10
+ * affects overall opacity. Partial relations have a continued
11
+ * backline ("more exist but aren't shown").
12
+ * 2. multiplicity + is_delimiting (per-member) — see
13
+ * partitive-multiplicity.ts for the registry + rakeStrokeStyle().
14
+ *
15
+ * This module owns:
16
+ * - relation-level color/badge (completeness-driven)
17
+ * - human-readable labels for badges
18
+ *
19
+ * It does NOT encode multiplicity visuals — those live in
20
+ * partitive-multiplicity.ts (separated so renderers can consume the
21
+ * pure rakeStrokeStyle() helper without pulling badge strings).
11
22
  */
12
23
 
13
- import type { TypeSharedPluralityWire } from '../adapters/types';
14
-
15
24
  const BASE_LIGHT = '#0d9488';
16
25
  const BASE_DARK = '#2dd4bf';
17
- const ACCENT_SHARED_LIGHT = '#3b82f6';
18
- const ACCENT_SHARED_DARK = '#60a5fa';
19
- const ACCENT_UNCERTAIN_LIGHT = '#f59e0b';
20
- const ACCENT_UNCERTAIN_DARK = '#fbbf24';
21
26
 
22
27
  export interface PartitiveRelationStyle {
23
28
  color: string;
@@ -25,38 +30,24 @@ export interface PartitiveRelationStyle {
25
30
  opacity: number;
26
31
  }
27
32
 
33
+ /**
34
+ * Relation-level style (color + opacity) driven by completeness.
35
+ *
36
+ * Per-member multiplicity + delimiting rendering is via
37
+ * `rakeStrokeStyle()` from partitive-multiplicity.ts, not here —
38
+ * this helper is for the relation's overall frame + header badges.
39
+ */
28
40
  export function partitiveRelationStyle(
29
41
  completeness: 'complete' | 'partial',
30
- plurality: TypeSharedPluralityWire | null,
31
42
  isDark: boolean,
32
43
  ): PartitiveRelationStyle {
33
- const base = isDark ? BASE_DARK : BASE_LIGHT;
34
- const opacity = completeness === 'partial' ? 0.6 : 1.0;
35
-
36
- let badgeClass = 'badge-teal';
37
- if (plurality?.isShared) {
38
- badgeClass = 'badge-blue';
39
- if (plurality.isUncertain) {
40
- badgeClass = 'badge-yellow';
41
- }
42
- }
43
-
44
- return { color: base, badgeClass, opacity };
45
- }
46
-
47
- export function pluralityColor(
48
- plurality: TypeSharedPluralityWire,
49
- isDark: boolean,
50
- ): string {
51
- if (plurality.isUncertain) return isDark ? ACCENT_UNCERTAIN_DARK : ACCENT_UNCERTAIN_LIGHT;
52
- if (plurality.isShared) return isDark ? ACCENT_SHARED_DARK : ACCENT_SHARED_LIGHT;
53
- return isDark ? BASE_DARK : BASE_LIGHT;
44
+ return {
45
+ color: isDark ? BASE_DARK : BASE_LIGHT,
46
+ badgeClass: 'badge-teal',
47
+ opacity: completeness === 'partial' ? 0.6 : 1.0,
48
+ };
54
49
  }
55
50
 
56
51
  export function completenessLabel(completeness: 'complete' | 'partial'): string {
57
52
  return completeness === 'partial' ? 'Partial' : 'Complete';
58
53
  }
59
-
60
- export function certaintyLabel(certainty: 'confirmed' | 'possible'): string {
61
- return certainty === 'possible' ? 'Possible' : 'Confirmed';
62
- }
@@ -5,6 +5,8 @@ import { useVocabularyStore } from '../stores/vocabulary';
5
5
  import { conceptUri } from '../adapters/model-bridge';
6
6
  import { UriRouter } from '../adapters/UriRouter';
7
7
  import type { PartitiveRelationWire, PartitiveMemberWire } from '../adapters/types';
8
+ import type { PartitiveMember } from 'glossarist/models';
9
+ import type { PartitiveMultiplicity } from '../utils/partitive-multiplicity';
8
10
  import ConceptDetail from '../components/ConceptDetail.vue';
9
11
  import RelationSphere from '../components/RelationSphere.vue';
10
12
  import ShortcutsModal from '../components/ShortcutsModal.vue';
@@ -80,15 +82,14 @@ const partitiveRelationsForSphere = computed<PartitiveRelationWire[]>(() => {
80
82
  : null;
81
83
  if (!compRef.id || !compTarget) continue;
82
84
  const partitives: PartitiveMemberWire[] = rel.partitives
83
- .map(member => {
85
+ .map((member): PartitiveMemberWire | null => {
84
86
  const ref = member.ref;
85
87
  if (!ref?.id) return null;
86
- const targetRegister = store.manifests.has(activeRegisterId.value)
87
- ? activeRegisterId.value
88
- : activeRegisterId.value;
89
- const uri = UriRouter.buildConceptUri(m.uriBase, targetRegister, ref.id);
88
+ const uri = UriRouter.buildConceptUri(m.uriBase, activeRegisterId.value, ref.id);
90
89
  if (uri === sourceUri) return null;
91
- return { uri, certainty: member.certainty };
90
+ const multiplicity: PartitiveMultiplicity = member.multiplicity;
91
+ const isDelimiting = member.isDelimiting;
92
+ return { uri, multiplicity, isDelimiting };
92
93
  })
93
94
  .filter((x): x is PartitiveMemberWire => x !== null);
94
95
  if (partitives.length === 0) continue;
@@ -97,13 +98,6 @@ const partitiveRelationsForSphere = computed<PartitiveRelationWire[]>(() => {
97
98
  comprehensive: compTarget,
98
99
  partitives,
99
100
  completeness: rel.completeness,
100
- plurality: rel.plurality
101
- ? {
102
- isShared: rel.plurality.isShared,
103
- isUncertain: rel.plurality.isUncertain,
104
- sharedType: null,
105
- }
106
- : null,
107
101
  register: activeRegisterId.value,
108
102
  });
109
103
  }
@@ -1,146 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { conceptFromJson } from '../adapters/model-bridge';
3
-
4
- describe('conceptFromJson — gl:partitiveRelations (v2)', () => {
5
- function makeJson(overrides: Record<string, unknown> = {}) {
6
- return {
7
- '@type': 'gl:Concept',
8
- '@id': 'https://example.org/test/concept/1.3',
9
- 'gl:identifier': '1.3',
10
- 'gl:localizedConcept': {},
11
- ...overrides,
12
- };
13
- }
14
-
15
- it('returns empty array when no partitiveRelations', () => {
16
- const c = conceptFromJson(makeJson());
17
- expect(c.partitiveRelations).toEqual([]);
18
- });
19
-
20
- it('parses comprehensive + 2 partitives', () => {
21
- const c = conceptFromJson(makeJson({
22
- 'gl:partitiveRelations': [{
23
- 'gl:comprehensive': { 'gl:source': 'urn:test', 'gl:id': '1.3' },
24
- 'gl:hasPartitive': [
25
- { 'gl:ref': { 'gl:source': 'urn:test', 'gl:id': '1.4' } },
26
- { 'gl:ref': { 'gl:source': 'urn:test', 'gl:id': '1.5' } },
27
- ],
28
- }],
29
- }));
30
- expect(c.partitiveRelations).toHaveLength(1);
31
- const r = c.partitiveRelations[0];
32
- expect(r.comprehensive.source).toBe('urn:test');
33
- expect(r.comprehensive.id).toBe('1.3');
34
- expect(r.partitives).toHaveLength(2);
35
- expect(r.partitives[0].ref.id).toBe('1.4');
36
- expect(r.partitives[1].ref.id).toBe('1.5');
37
- });
38
-
39
- it('parses completeness', () => {
40
- const c = conceptFromJson(makeJson({
41
- 'gl:partitiveRelations': [{
42
- 'gl:comprehensive': { 'gl:source': 'urn:test', 'gl:id': '1.3' },
43
- 'gl:hasPartitive': [
44
- { 'gl:ref': { 'gl:source': 'urn:test', 'gl:id': '1.4' } },
45
- { 'gl:ref': { 'gl:source': 'urn:test', 'gl:id': '1.5' } },
46
- ],
47
- 'gl:completeness': 'partial',
48
- }],
49
- }));
50
- expect(c.partitiveRelations[0].completeness).toBe('partial');
51
- expect(c.partitiveRelations[0].isPartial).toBe(true);
52
- });
53
-
54
- it('parses per-member certainty', () => {
55
- const c = conceptFromJson(makeJson({
56
- 'gl:partitiveRelations': [{
57
- 'gl:comprehensive': { 'gl:source': 'urn:test', 'gl:id': '1.3' },
58
- 'gl:hasPartitive': [
59
- { 'gl:ref': { 'gl:source': 'urn:test', 'gl:id': '1.4' } },
60
- { 'gl:ref': { 'gl:source': 'urn:test', 'gl:id': '1.5' }, 'gl:certainty': 'possible' },
61
- ],
62
- }],
63
- }));
64
- const members = c.partitiveRelations[0].partitives;
65
- expect(members[0].certainty).toBe('confirmed');
66
- expect(members[1].certainty).toBe('possible');
67
- expect(members[1].isPossible).toBe(true);
68
- });
69
-
70
- it('parses plurality block', () => {
71
- const c = conceptFromJson(makeJson({
72
- 'gl:partitiveRelations': [{
73
- 'gl:comprehensive': { 'gl:source': 'urn:test', 'gl:id': '1.3' },
74
- 'gl:hasPartitive': [
75
- { 'gl:ref': { 'gl:source': 'urn:test', 'gl:id': '1.4' } },
76
- { 'gl:ref': { 'gl:source': 'urn:test', 'gl:id': '1.5' } },
77
- ],
78
- 'gl:hasPlurality': {
79
- 'gl:isShared': true,
80
- 'gl:isUncertain': true,
81
- 'gl:sharedType': { 'gl:source': 'urn:test', 'gl:id': '1.99' },
82
- },
83
- }],
84
- }));
85
- const p = c.partitiveRelations[0].plurality;
86
- expect(p).not.toBeNull();
87
- expect(p?.isShared).toBe(true);
88
- expect(p?.isUncertain).toBe(true);
89
- expect(p?.sharedType?.id).toBe('1.99');
90
- });
91
-
92
- it('parses criterion as localized hash', () => {
93
- const c = conceptFromJson(makeJson({
94
- 'gl:partitiveRelations': [{
95
- 'gl:comprehensive': { 'gl:source': 'urn:test', 'gl:id': '1.3' },
96
- 'gl:hasPartitive': [
97
- { 'gl:ref': { 'gl:source': 'urn:test', 'gl:id': '1.4' } },
98
- { 'gl:ref': { 'gl:source': 'urn:test', 'gl:id': '1.5' } },
99
- ],
100
- 'gl:criterion': { eng: 'physical structure', fra: 'structure physique' },
101
- }],
102
- }));
103
- expect(c.partitiveRelations[0].criterion?.eng).toBe('physical structure');
104
- expect(c.partitiveRelations[0].criterion?.fra).toBe('structure physique');
105
- });
106
-
107
- it('parses criterion as plain string (normalized to default)', () => {
108
- const c = conceptFromJson(makeJson({
109
- 'gl:partitiveRelations': [{
110
- 'gl:comprehensive': { 'gl:source': 'urn:test', 'gl:id': '1.3' },
111
- 'gl:hasPartitive': [
112
- { 'gl:ref': { 'gl:source': 'urn:test', 'gl:id': '1.4' } },
113
- { 'gl:ref': { 'gl:source': 'urn:test', 'gl:id': '1.5' } },
114
- ],
115
- 'gl:criterion': 'a bare criterion string',
116
- }],
117
- }));
118
- expect(c.partitiveRelations[0].criterion?.default).toBe('a bare criterion string');
119
- });
120
-
121
- it('skips relation with no comprehensive', () => {
122
- const c = conceptFromJson(makeJson({
123
- 'gl:partitiveRelations': [{
124
- 'gl:hasPartitive': [
125
- { 'gl:ref': { 'gl:source': 'urn:test', 'gl:id': '1.4' } },
126
- ],
127
- }],
128
- }));
129
- expect(c.partitiveRelations).toEqual([]);
130
- });
131
-
132
- it('skips relation with < 2 valid partitives', () => {
133
- const c = conceptFromJson(makeJson({
134
- 'gl:partitiveRelations': [{
135
- 'gl:comprehensive': { 'gl:source': 'urn:test', 'gl:id': '1.3' },
136
- 'gl:hasPartitive': [
137
- { 'gl:ref': { 'gl:source': 'urn:test', 'gl:id': '1.4' } },
138
- // Single member — PartitiveRelation constructor throws
139
- ],
140
- }],
141
- }));
142
- // The constructor throws before we catch it, so the relation is dropped
143
- // via the runtime error path. conceptFromJson should not propagate.
144
- expect(c.partitiveRelations).toEqual([]);
145
- });
146
- });