@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.
@@ -17,6 +17,7 @@
17
17
  // block when upstream PRs the d.ts changes.
18
18
 
19
19
  import type { ConceptSource, Citation, GlossaristModel } from 'glossarist';
20
+ import type { PartitiveMultiplicity } from '../../utils/partitive-multiplicity';
20
21
 
21
22
  declare module 'glossarist' {
22
23
  class RegistrableModel extends GlossaristModel {
@@ -168,29 +169,42 @@ declare module 'glossarist' {
168
169
  static fromJSON(data: Record<string, unknown>): TypeSharedPlurality;
169
170
  }
170
171
 
171
- class PartitiveMember extends GlossaristModel {
172
- constructor(data?: { ref?: ConceptRef; certainty?: MemberCertainty });
172
+ interface Concept {
173
+ readonly partitiveRelations: PartitiveRelation[];
174
+ }
175
+
176
+ // ── v3 PartitiveRelation (glossarist 0.4.24 — ISO 704:2022 multiplicity + delimiting) ─
177
+ // Re-exposed here so callers importing from the top-level `glossarist`
178
+ // entry get the v3-augmented shape, not the stale d.ts type.
179
+ export class PartitiveMember extends GlossaristModel {
180
+ constructor(data?: {
181
+ ref?: ConceptRef;
182
+ multiplicity?: PartitiveMultiplicity;
183
+ is_delimiting?: boolean;
184
+ });
173
185
  readonly ref: ConceptRef;
174
- readonly certainty: MemberCertainty;
186
+ readonly multiplicity: PartitiveMultiplicity;
187
+ readonly is_delimiting: boolean;
175
188
  readonly isConfirmed: boolean;
176
189
  readonly isPossible: boolean;
177
- toJSON(): { ref: ReturnType<ConceptRef['toJSON']>; certainty?: MemberCertainty };
190
+ readonly isCompulsory: boolean;
191
+ readonly isOptional: boolean;
192
+ get isDelimiting(): boolean;
193
+ toJSON(): { ref: ReturnType<ConceptRef['toJSON']>; multiplicity?: PartitiveMultiplicity; is_delimiting?: boolean };
178
194
  static fromJSON(data: Record<string, unknown>): PartitiveMember;
179
195
  static identityOf(value: unknown): string;
180
196
  }
181
197
 
182
- class PartitiveRelation extends GlossaristModel {
198
+ export class PartitiveRelation extends GlossaristModel {
183
199
  constructor(data?: {
184
200
  comprehensive?: ConceptRef;
185
201
  partitives?: PartitiveMember[];
186
202
  completeness?: Completeness;
187
- plurality?: TypeSharedPlurality;
188
203
  criterion?: Record<string, string> | string;
189
204
  });
190
205
  readonly comprehensive: ConceptRef;
191
206
  readonly partitives: PartitiveMember[];
192
207
  readonly completeness: Completeness;
193
- readonly plurality: TypeSharedPlurality | null;
194
208
  readonly criterion: Record<string, string> | null;
195
209
  readonly isComplete: boolean;
196
210
  readonly isPartial: boolean;
@@ -201,16 +215,11 @@ declare module 'glossarist' {
201
215
  comprehensive: ReturnType<ConceptRef['toJSON']>;
202
216
  partitives: ReturnType<PartitiveMember['toJSON']>[];
203
217
  completeness: Completeness;
204
- plurality?: ReturnType<TypeSharedPlurality['toJSON']>;
205
218
  criterion?: Record<string, string>;
206
219
  };
207
220
  static fromJSON(data: Record<string, unknown>): PartitiveRelation;
208
221
  static identityOf(value: unknown): string;
209
222
  }
210
-
211
- interface Concept {
212
- readonly partitiveRelations: PartitiveRelation[];
213
- }
214
223
  }
215
224
 
216
225
  // Re-declare the same exports under the 'glossarist/models' subpath so
@@ -246,13 +255,23 @@ declare module 'glossarist/models' {
246
255
  static fromJSON(data: Record<string, unknown>): TypeSharedPlurality;
247
256
  }
248
257
 
258
+ // ── v2 PartitiveRelation (glossarist 0.4.24 — ISO 704:2022 multiplicity + delimiting) ─
259
+
249
260
  export class PartitiveMember extends GlossaristModel {
250
- constructor(data?: { ref?: ConceptRef; certainty?: MemberCertainty });
261
+ constructor(data?: {
262
+ ref?: ConceptRef;
263
+ multiplicity?: PartitiveMultiplicity;
264
+ is_delimiting?: boolean;
265
+ });
251
266
  readonly ref: ConceptRef;
252
- readonly certainty: MemberCertainty;
267
+ readonly multiplicity: PartitiveMultiplicity;
268
+ readonly is_delimiting: boolean;
253
269
  readonly isConfirmed: boolean;
254
270
  readonly isPossible: boolean;
255
- toJSON(): { ref: ReturnType<ConceptRef['toJSON']>; certainty?: MemberCertainty };
271
+ readonly isCompulsory: boolean;
272
+ readonly isOptional: boolean;
273
+ get isDelimiting(): boolean;
274
+ toJSON(): { ref: ReturnType<ConceptRef['toJSON']>; multiplicity?: PartitiveMultiplicity; is_delimiting?: boolean };
256
275
  static fromJSON(data: Record<string, unknown>): PartitiveMember;
257
276
  static identityOf(value: unknown): string;
258
277
  }
@@ -262,13 +281,11 @@ declare module 'glossarist/models' {
262
281
  comprehensive?: ConceptRef;
263
282
  partitives?: PartitiveMember[];
264
283
  completeness?: Completeness;
265
- plurality?: TypeSharedPlurality;
266
284
  criterion?: Record<string, string> | string;
267
285
  });
268
286
  readonly comprehensive: ConceptRef;
269
287
  readonly partitives: PartitiveMember[];
270
288
  readonly completeness: Completeness;
271
- readonly plurality: TypeSharedPlurality | null;
272
289
  readonly criterion: Record<string, string> | null;
273
290
  readonly isComplete: boolean;
274
291
  readonly isPartial: boolean;
@@ -279,7 +296,6 @@ declare module 'glossarist/models' {
279
296
  comprehensive: ReturnType<ConceptRef['toJSON']>;
280
297
  partitives: ReturnType<PartitiveMember['toJSON']>[];
281
298
  completeness: Completeness;
282
- plurality?: ReturnType<TypeSharedPlurality['toJSON']>;
283
299
  criterion?: Record<string, string>;
284
300
  };
285
301
  static fromJSON(data: Record<string, unknown>): PartitiveRelation;
@@ -168,20 +168,14 @@ export interface PartitiveRelationWire {
168
168
  comprehensive: string;
169
169
  partitives: PartitiveMemberWire[];
170
170
  completeness: 'complete' | 'partial';
171
- plurality: TypeSharedPluralityWire | null;
172
171
  criterion?: Record<string, string>;
173
172
  register: string;
174
173
  }
175
174
 
176
175
  export interface PartitiveMemberWire {
177
176
  uri: string;
178
- certainty: 'confirmed' | 'possible';
179
- }
180
-
181
- export interface TypeSharedPluralityWire {
182
- isShared: boolean;
183
- isUncertain: boolean;
184
- sharedType?: string | null;
177
+ multiplicity: 'compulsory' | 'optional' | 'compulsory_multiple' | 'optional_multiple' | 'compulsory_at_least_one';
178
+ isDelimiting: boolean;
185
179
  }
186
180
 
187
181
  export interface GraphNode {
@@ -1,46 +1,63 @@
1
1
  <script setup lang="ts">
2
2
  /**
3
3
  * PartitiveRelationDiagram — renders a single PartitiveRelation as an
4
- * ISO 704 rake/bracket diagram in SVG.
4
+ * ISO 704:2022 rake/bracket diagram in SVG.
5
5
  *
6
- * Layout (looking top-down):
6
+ * Layout:
7
7
  *
8
8
  * [comprehensive]
9
9
  * │
10
- * │ ← vertical stem
11
- * ┌───────────────┴───────────────┐ ← horizontal spine
10
+ * │ ← stem (always single solid — relation frame)
11
+ * ┌───────────────┴───────────────┐ ← spine (always single solid — relation frame)
12
12
  * │ │ │
13
- * │ │ │ ← perpendicular drops
13
+ * │ │ │ ← drops (per-member multiplicity + delimiting)
14
14
  * [part1] [part2] [part3]
15
15
  *
16
- * Line conventions per ISO 704:
17
- * - default : single solid line
18
- * - plurality.isShared && !isUncertain : close-set DOUBLE solid lines
19
- * on stem + spine (drops stay single)
20
- * - plurality.isShared && isUncertain : one solid + one DASHED line
21
- * - completeness === 'partial' : spine extends past the last
22
- * tooth (continued backline)
16
+ * Per ISO 704:2022, each drop's line style encodes its multiplicity +
17
+ * is_delimiting (see utils/partitive-multiplicity.ts for the registry):
18
+ *
19
+ * multiplicity is_delimiting visual
20
+ * ------------------- -------------- --------------------------------
21
+ * compulsory false 1 solid @ 1.5px
22
+ * compulsory true 1 solid @ 4.5px (bold, 3× width)
23
+ * optional false 1 dashed @ 1.5px
24
+ * optional true 1 dashed @ 4.5px
25
+ * compulsory_multiple false 2 solid @ 1.5px
26
+ * compulsory_multiple true 2 solid @ 4.5px
27
+ * optional_multiple false 2 dashed @ 1.5px
28
+ * optional_multiple true 2 dashed @ 4.5px
29
+ * compulsory_at_least_one false 1 solid + 1 dashed @ 1.5px
30
+ * compulsory_at_least_one true 1 solid + 1 dashed @ 4.5px
31
+ *
32
+ * completeness === 'partial' : spine extends past the last tooth
33
+ * (continued backline — "more exist but
34
+ * not shown")
23
35
  *
24
36
  * Node sizing is designation-aware: the longest label drives the node
25
37
  * width (clamped to [MIN_NODE_W, MAX_NODE_W]); labels that don't fit
26
- * wrap to a second line via tspan. This lets the rake show actual
27
- * designations ('system of quantities') instead of bare IDs ('1.3').
38
+ * wrap to a second line via tspan.
28
39
  */
29
40
  import { computed } from 'vue';
30
- import type { TypeSharedPluralityWire } from '../adapters/types';
41
+ import {
42
+ rakeStrokeStyle,
43
+ type PartitiveMultiplicity,
44
+ type RakeStrokeStyle,
45
+ } from '../utils/partitive-multiplicity';
31
46
 
32
47
  export interface PartitiveMemberLabeled {
33
48
  uri: string;
34
49
  label: string;
35
- certainty: 'confirmed' | 'possible';
50
+ multiplicity: PartitiveMultiplicity;
51
+ isDelimiting: boolean;
36
52
  }
37
53
 
38
54
  const props = withDefaults(defineProps<{
39
55
  comprehensiveLabel: string;
40
56
  partitives: PartitiveMemberLabeled[];
41
57
  completeness: 'complete' | 'partial';
42
- plurality: TypeSharedPluralityWire | null;
43
58
  criterion?: string | null;
59
+ /** Optional per-member tooltip string. */
60
+ memberTooltip?: (m: { multiplicity: PartitiveMultiplicity; isDelimiting: boolean }) => string;
44
61
  }>(), {
45
62
  criterion: null,
46
63
  });
@@ -59,29 +76,19 @@ const SPINE_TO_NODE = 32;
59
76
  const GAP_X = 16;
60
77
  const PADDING_X = 20;
61
78
  const PADDING_Y = 14;
62
- const DOUBLE_GAP = 3;
79
+ const DOUBLE_GAP = 3; // gap between the two lines of a close-set double
63
80
  const PARTIAL_TAIL = 32;
64
81
  const DASH_PATTERN = '4 3';
65
- const CHAR_W = 6.2; // approx for 11px sans-serif
66
- const NODE_TEXT_PAD = 16; // horizontal padding inside node rect
67
-
68
- type LineVariant = 'single' | 'double-solid' | 'solid-plus-dashed';
69
-
70
- const lineVariant = computed<LineVariant>(() => {
71
- if (!props.plurality?.isShared) return 'single';
72
- return props.plurality.isUncertain ? 'solid-plus-dashed' : 'double-solid';
73
- });
82
+ const CHAR_W = 6.2; // approx for 11px sans-serif
83
+ const NODE_TEXT_PAD = 16;
84
+ const FRAME_STROKE_WIDTH = 1.4; // stem + spine use this width always
74
85
 
75
86
  /** Estimated pixel width of a string at 11px sans-serif. */
76
87
  function estimateTextWidth(text: string): number {
77
88
  return text.length * CHAR_W;
78
89
  }
79
90
 
80
- /**
81
- * Wrap a label to at most 2 lines, breaking on word boundaries.
82
- * If a single word exceeds the limit, it stays on its own line and
83
- * gets truncated with an ellipsis.
84
- */
91
+ /** Wrap a label to at most 2 lines, breaking on word boundaries. */
85
92
  function wrapLabel(text: string, maxCharsPerLine: number): string[] {
86
93
  if (text.length <= maxCharsPerLine) return [text];
87
94
  const words = text.split(/\s+/);
@@ -89,7 +96,6 @@ function wrapLabel(text: string, maxCharsPerLine: number): string[] {
89
96
  let current = '';
90
97
  for (const w of words) {
91
98
  if (w.length > maxCharsPerLine) {
92
- // word itself too long — flush current, push truncated word
93
99
  if (current) { lines.push(current); current = ''; }
94
100
  lines.push(w.slice(0, Math.max(1, maxCharsPerLine - 1)) + '…');
95
101
  continue;
@@ -132,7 +138,12 @@ const layout = computed(() => {
132
138
  const partitiveSlots = props.partitives.map((member, i) => {
133
139
  const x = PADDING_X + nodeW / 2 + i * (nodeW + GAP_X);
134
140
  const labelLines = wrapped[i + 1]; // index 0 is comprehensive
135
- return { member, x, labelLines };
141
+ return {
142
+ member,
143
+ x,
144
+ labelLines,
145
+ style: rakeStrokeStyle(member.multiplicity, member.isDelimiting),
146
+ };
136
147
  });
137
148
 
138
149
  const leftX = partitiveSlots[0]?.x ?? centerX;
@@ -157,19 +168,38 @@ const layout = computed(() => {
157
168
  };
158
169
  });
159
170
 
160
- function strokeAttr(variant: LineVariant, opacity = 1, override?: 'dash') {
161
- const useDash = override === 'dash' || variant === 'solid-plus-dashed';
171
+ /**
172
+ * Build SVG stroke attributes for the relation frame (stem + spine).
173
+ *
174
+ * The frame is structural — always a single solid line at standard
175
+ * width. Per-member multiplicity + delimiting are encoded on the drops
176
+ * only (MECE: frame vs drops).
177
+ */
178
+ const frameStrokeAttrs = computed(() => ({
179
+ stroke: 'currentColor',
180
+ 'stroke-width': FRAME_STROKE_WIDTH,
181
+ }));
182
+
183
+ /**
184
+ * Build SVG stroke attributes for one of a drop's parallel lines.
185
+ * The drop may have 1 or 2 parallel lines per its multiplicity.
186
+ */
187
+ function dropStrokeAttrs(style: RakeStrokeStyle, dashed: boolean, opacity = 1) {
162
188
  return {
163
189
  stroke: 'currentColor',
164
- 'stroke-width': 1.4,
190
+ 'stroke-width': style.strokeWidth,
165
191
  opacity,
166
- ...(useDash ? { 'stroke-dasharray': DASH_PATTERN } : {}),
192
+ ...(dashed ? { 'stroke-dasharray': DASH_PATTERN } : {}),
167
193
  };
168
194
  }
169
195
 
170
196
  function emitNav(uri: string) {
171
197
  emit('navigate', uri);
172
198
  }
199
+
200
+ function tooltipFor(member: PartitiveMemberLabeled): string | undefined {
201
+ return props.memberTooltip?.({ multiplicity: member.multiplicity, isDelimiting: member.isDelimiting });
202
+ }
173
203
  </script>
174
204
 
175
205
  <template>
@@ -212,71 +242,53 @@ function emitNav(uri: string) {
212
242
  class="text-[9px] italic fill-emerald-800 dark:fill-emerald-200/80"
213
243
  >{{ criterion }}</text>
214
244
 
215
- <!-- Stem (vertical from comprehensive to spine) -->
245
+ <!-- Stem: comprehensive → spine (single solid frame) -->
216
246
  <line
217
- v-if="lineVariant === 'single'"
218
247
  :x1="layout.centerX" :y1="layout.compY + layout.nodeH / 2"
219
248
  :x2="layout.centerX" :y2="layout.spineY"
220
- v-bind="strokeAttr('single')"
249
+ v-bind="frameStrokeAttrs"
221
250
  />
222
- <template v-else>
223
- <line
224
- :x1="layout.centerX - DOUBLE_GAP / 2" :y1="layout.compY + layout.nodeH / 2"
225
- :x2="layout.centerX - DOUBLE_GAP / 2" :y2="layout.spineY"
226
- v-bind="strokeAttr('double-solid')"
227
- />
228
- <line
229
- :x1="layout.centerX + DOUBLE_GAP / 2" :y1="layout.compY + layout.nodeH / 2"
230
- :x2="layout.centerX + DOUBLE_GAP / 2" :y2="layout.spineY"
231
- v-bind="strokeAttr(lineVariant, 1, lineVariant === 'solid-plus-dashed' ? 'dash' : undefined)"
232
- />
233
- </template>
234
251
 
235
- <!-- Spine (horizontal, from leftmost tooth to rightmost tooth or extended) -->
252
+ <!-- Spine: leftmost tooth → rightmost tooth (single solid frame;
253
+ extended for completeness: 'partial') -->
236
254
  <line
237
- v-if="lineVariant === 'single'"
238
255
  :x1="layout.leftX" :y1="layout.spineY"
239
256
  :x2="layout.spineEndX" :y2="layout.spineY"
240
- v-bind="strokeAttr('single')"
257
+ v-bind="frameStrokeAttrs"
241
258
  />
242
- <template v-else>
243
- <line
244
- :x1="layout.leftX" :y1="layout.spineY - DOUBLE_GAP / 2"
245
- :x2="layout.spineEndX" :y2="layout.spineY - DOUBLE_GAP / 2"
246
- v-bind="strokeAttr('double-solid')"
247
- />
248
- <line
249
- :x1="layout.leftX" :y1="layout.spineY + DOUBLE_GAP / 2"
250
- :x2="layout.spineEndX" :y2="layout.spineY + DOUBLE_GAP / 2"
251
- v-bind="strokeAttr(lineVariant, 1, lineVariant === 'solid-plus-dashed' ? 'dash' : undefined)"
252
- />
253
- </template>
254
259
 
255
260
  <!-- Drops + partitive nodes -->
256
261
  <g v-for="(slot, i) in layout.partitiveSlots" :key="i">
262
+ <!-- Primary drop line -->
257
263
  <line
258
264
  :x1="slot.x" :y1="layout.spineY"
259
265
  :x2="slot.x" :y2="layout.partY - layout.nodeH / 2"
260
- v-bind="strokeAttr('single', slot.member.certainty === 'possible' ? 0.5 : 1, slot.member.certainty === 'possible' ? 'dash' : undefined)"
266
+ v-bind="dropStrokeAttrs(slot.style, slot.style.primaryDashed)"
267
+ />
268
+ <!-- Secondary drop line (only for lineCount === 2; e.g. *_multiple, compulsory_at_least_one) -->
269
+ <line
270
+ v-if="slot.style.lineCount === 2"
271
+ :x1="slot.x + DOUBLE_GAP" :y1="layout.spineY"
272
+ :x2="slot.x + DOUBLE_GAP" :y2="layout.partY - layout.nodeH / 2"
273
+ v-bind="dropStrokeAttrs(slot.style, slot.style.secondaryDashed)"
261
274
  />
262
- <g class="cursor-pointer" @click="emitNav(slot.member.uri)">
275
+ <g class="cursor-pointer" :title="tooltipFor(slot.member)" @click="emitNav(slot.member.uri)">
263
276
  <rect
264
277
  :x="slot.x - layout.nodeW / 2"
265
278
  :y="layout.partY - layout.nodeH / 2"
266
279
  :width="layout.nodeW"
267
280
  :height="layout.nodeH"
268
281
  rx="6"
269
- :class="slot.member.certainty === 'possible'
270
- ? 'fill-emerald-50/60 dark:fill-emerald-900/20 stroke-current'
282
+ :class="slot.style.strokeWidth > 2
283
+ ? 'fill-emerald-100 dark:fill-emerald-900/50 stroke-current'
271
284
  : 'fill-emerald-50 dark:fill-emerald-900/30 stroke-current'"
272
- :stroke-dasharray="slot.member.certainty === 'possible' ? DASH_PATTERN : undefined"
273
- stroke-width="1.2"
285
+ :stroke-width="slot.style.strokeWidth > 2 ? 2.4 : 1.2"
274
286
  />
275
287
  <text
276
288
  :x="slot.x"
277
289
  text-anchor="middle"
278
- :class="slot.member.certainty === 'possible'
279
- ? 'text-[11px] fill-emerald-700/70 dark:fill-emerald-200/60 italic'
290
+ :class="slot.style.strokeWidth > 2
291
+ ? 'text-[11px] fill-emerald-900 dark:fill-emerald-50 font-medium'
280
292
  : 'text-[11px] fill-emerald-900 dark:fill-emerald-100'"
281
293
  >
282
294
  <tspan
@@ -1,25 +1,25 @@
1
1
  <script setup lang="ts">
2
2
  /**
3
3
  * PartitiveRelationList — renders one-to-many partitive decompositions
4
- * as ISO 704 rake diagrams. Each relation shows:
5
- * - completeness badge (complete / partial)
6
- * - plurality badge (shared / uncertain / sharedType) when present
7
- * - criterion as italic text under the header
8
- * - the rake diagram itself (PartitiveRelationDiagram)
4
+ * as ISO 704 rake diagrams. Each relation card shows:
5
+ * - completeness badge (Complete / Partial)
6
+ * - the comprehensive's designation as the card title
7
+ * - optional criterion as italic text
8
+ * - the rake diagram itself (PartitiveRelationDiagram), where each
9
+ * drop's line style encodes its multiplicity + is_delimiting per
10
+ * ISO 704:2022 (5 multiplicity values × 2 delimiting states).
9
11
  *
10
- * The diagram is the primary renderer. Badges in the header communicate
11
- * the metadata that the diagram's line styles also encode (so users who
12
- * can't perceive the visual difference still get the info).
12
+ * Header badges communicate metadata that the diagram's line styles
13
+ * also encode — for users who can't perceive the visual difference
14
+ * (screen readers, low vision, monochrome displays).
13
15
  */
14
16
  import type { PartitiveRelationWire, Manifest } from '../adapters/types';
15
17
  import { useRouter } from 'vue-router';
16
18
  import { useVocabularyStore } from '../stores/vocabulary';
17
19
  import { getFactory } from '../adapters/factory';
18
20
  import { useI18n, locale } from '../i18n';
19
- import {
20
- completenessLabel,
21
- certaintyLabel,
22
- } from '../utils/partitive-relation-styling';
21
+ import { completenessLabel } from '../utils/partitive-relation-styling';
22
+ import { multiplicityDefinition } from '../utils/partitive-multiplicity';
23
23
  import PartitiveRelationDiagram, {
24
24
  type PartitiveMemberLabeled,
25
25
  } from './PartitiveRelationDiagram.vue';
@@ -77,25 +77,24 @@ function criterionText(criterion?: Record<string, string>): string | null {
77
77
  ?? null;
78
78
  }
79
79
 
80
- function pluralityBadge(plurality: NonNullable<PartitiveRelationWire['plurality']>): string {
81
- if (!plurality.isShared) return '';
82
- let label = t('partitive.plurality.shared') || 'shared type';
83
- if (plurality.isUncertain) {
84
- label = `${label} (${t('partitive.plurality.uncertain') || 'uncertain'})`;
85
- }
86
- if (plurality.sharedType) {
87
- label = `${label}: ${plurality.sharedType}`;
88
- }
89
- return label;
90
- }
91
-
80
+ /**
81
+ * Build a per-member view-model carrying the resolved designation plus
82
+ * the multiplicity + is_delimiting fields the diagram needs.
83
+ */
92
84
  function labeledMembers(rel: PartitiveRelationWire): PartitiveMemberLabeled[] {
93
85
  return rel.partitives.map(m => ({
94
86
  uri: m.uri,
95
87
  label: designationFor(m.uri),
96
- certainty: m.certainty,
88
+ multiplicity: m.multiplicity,
89
+ isDelimiting: m.isDelimiting,
97
90
  }));
98
91
  }
92
+
93
+ /** Tooltip text for a member, shown on hover. */
94
+ function memberTooltip(m: { multiplicity: string; isDelimiting: boolean }): string {
95
+ const def = multiplicityDefinition(m.multiplicity as Parameters<typeof multiplicityDefinition>[0]);
96
+ return m.isDelimiting ? `Delimiting ${def.label.toLowerCase()}` : def.label;
97
+ }
99
98
  </script>
100
99
 
101
100
  <template>
@@ -113,13 +112,6 @@ function labeledMembers(rel: PartitiveRelationWire): PartitiveMemberLabeled[] {
113
112
  <span class="text-[10px] font-mono px-1.5 py-0.5 rounded bg-emerald-200 dark:bg-emerald-800 text-emerald-900 dark:text-emerald-100 uppercase">
114
113
  {{ completenessLabel(rel.completeness) }}
115
114
  </span>
116
- <span
117
- v-if="rel.plurality && rel.plurality.isShared"
118
- class="text-[10px] font-mono px-1.5 py-0.5 rounded bg-amber-200 dark:bg-amber-800 text-amber-900 dark:text-amber-100 uppercase"
119
- :title="pluralityBadge(rel.plurality)"
120
- >
121
- {{ pluralityBadge(rel.plurality) }}
122
- </span>
123
115
  <button
124
116
  @click="navigate(rel.comprehensive)"
125
117
  class="ml-auto text-sm font-medium text-emerald-900 dark:text-emerald-100 hover:underline truncate"
@@ -133,14 +125,14 @@ function labeledMembers(rel: PartitiveRelationWire): PartitiveMemberLabeled[] {
133
125
  >
134
126
  {{ criterionText(rel.criterion) }}
135
127
  </div>
136
- <!-- Rake diagram (ISO 704 partitive-relation convention) -->
128
+ <!-- Rake diagram (ISO 704:2022 partitive-relation convention) -->
137
129
  <div class="px-3 py-3 overflow-x-auto">
138
130
  <PartitiveRelationDiagram
139
131
  :comprehensive-label="designationFor(rel.comprehensive)"
140
132
  :partitives="labeledMembers(rel)"
141
133
  :completeness="rel.completeness"
142
- :plurality="rel.plurality"
143
134
  :criterion="null"
135
+ :member-tooltip="memberTooltip"
144
136
  @navigate="navigate"
145
137
  />
146
138
  </div>