@glossarist/concept-browser 0.7.86 → 0.7.87

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,4 +1,4 @@
1
- // Local module augmentation for glossarist 0.4.2.
1
+ // Local module augmentation for glossarist 0.4.26.
2
2
  //
3
3
  // Upstream's published src/models/index.d.ts declares ZERO classes for the
4
4
  // non-verbal hierarchy (Figure, Table, Formula, FigureImage, NonVerbalEntity,
@@ -9,15 +9,15 @@
9
9
  //
10
10
  // This file declares the runtime shape so consumer code can be type-checked.
11
11
  // DELETE this file when upstream ships proper declarations — tracked by
12
- // PR glossarist/glossarist-js#31 (targets v0.4.3+).
12
+ // PR glossarist/glossarist-js#31.
13
13
  //
14
- // v2 PartitiveRelation block (below) tracks glossarist 0.4.20 — the runtime
15
- // exports PartitiveRelation/PartitiveMember/TypeSharedPlurality from
16
- // src/models/index.js, but the d.ts has not been updated. Remove the v2
17
- // block when upstream PRs the d.ts changes.
14
+ // v2 PartitiveRelation block (below) tracks glossarist 0.4.26 — the runtime
15
+ // exports PartitiveRelation/PartitiveMember from src/models/index.js, but
16
+ // the d.ts has not been updated. Remove the v2 block when upstream PRs the
17
+ // d.ts changes.
18
18
 
19
19
  import type { ConceptSource, Citation, GlossaristModel } from 'glossarist';
20
- import type { PartitiveMultiplicity } from '../../utils/partitive-multiplicity';
20
+ import type { PartitivePresence, PartitiveCount } from '../../utils/partitive-multiplicity';
21
21
 
22
22
  declare module 'glossarist' {
23
23
  class RegistrableModel extends GlossaristModel {
@@ -137,8 +137,9 @@ declare module 'glossarist' {
137
137
  static fromJSON(data: Record<string, unknown>): BibliographyData;
138
138
  }
139
139
 
140
- // ── v2 PartitiveRelation (glossarist 0.4.20) ────────────────────────────
140
+ // ── PartitiveRelation (glossarist 0.4.26 — MECE presence × count) ─
141
141
  // Runtime exports these from src/models/index.js; d.ts is stale.
142
+ // (Tracked by PR glossarist/glossarist-js#31.)
142
143
 
143
144
  type Completeness = 'complete' | 'partial';
144
145
  const COMPLETENESS: { readonly COMPLETE: 'complete'; readonly PARTIAL: 'partial' };
@@ -146,51 +147,49 @@ declare module 'glossarist' {
146
147
  const DEFAULT_COMPLETENESS: Completeness;
147
148
  function isValidCompleteness(value: unknown): value is Completeness;
148
149
 
149
- type MemberCertainty = 'confirmed' | 'possible';
150
- const MEMBER_CERTAINTY: { readonly CONFIRMED: 'confirmed'; readonly POSSIBLE: 'possible' };
151
- const MEMBER_CERTAINTY_VALUES: readonly MemberCertainty[];
152
- const DEFAULT_MEMBER_CERTAINTY: MemberCertainty;
153
- function isValidMemberCertainty(value: unknown): value is MemberCertainty;
150
+ const PARTITIVE_PRESENCE: { readonly REQUIRED: 'required'; readonly OPTIONAL: 'optional' } & { readonly VALUES: readonly ['required', 'optional'] };
151
+ type PartitivePresence = (typeof PARTITIVE_PRESENCE.VALUES)[number];
152
+ const PARTITIVE_PRESENCE_VALUES: readonly PartitivePresence[];
153
+ const DEFAULT_PRESENCE: PartitivePresence;
154
+ function isValidPresence(value: unknown): value is PartitivePresence;
154
155
 
155
- class TypeSharedPlurality extends GlossaristModel {
156
- constructor(data?: {
157
- isShared?: boolean;
158
- is_shared?: boolean;
159
- isUncertain?: boolean;
160
- is_uncertain?: boolean;
161
- sharedType?: ConceptRef;
162
- shared_type?: ConceptRef;
163
- });
164
- readonly isShared: boolean;
165
- readonly isUncertain: boolean;
166
- readonly sharedType: ConceptRef | null;
167
- hasSharedType(): boolean;
168
- toJSON(): { is_shared: boolean; is_uncertain?: boolean; shared_type?: ReturnType<ConceptRef['toJSON']> };
169
- static fromJSON(data: Record<string, unknown>): TypeSharedPlurality;
170
- }
156
+ const PARTITIVE_COUNT: { readonly EXACTLY_ONE: 'exactly_one'; readonly AT_LEAST_ONE: 'at_least_one'; readonly MULTIPLE: 'multiple' } & { readonly VALUES: readonly ['exactly_one', 'at_least_one', 'multiple'] };
157
+ type PartitiveCount = (typeof PARTITIVE_COUNT.VALUES)[number];
158
+ const PARTITIVE_COUNT_VALUES: readonly PartitiveCount[];
159
+ const DEFAULT_COUNT: PartitiveCount;
160
+ function isValidCount(value: unknown): value is PartitiveCount;
161
+
162
+ type Multiplicity =
163
+ | 'compulsory' | 'optional' | 'compulsory_multiple'
164
+ | 'optional_multiple' | 'compulsory_at_least_one';
165
+ const MULTIPLICITY: Record<string, Multiplicity>;
166
+ const MULTIPLICITY_VALUES: readonly Multiplicity[];
167
+ const DEFAULT_MULTIPLICITY: Multiplicity;
168
+ function isValidMultiplicity(value: unknown): value is Multiplicity;
169
+ function multiplicityFromPair(presence: PartitivePresence, count: PartitiveCount): Multiplicity;
170
+ function pairFromMultiplicity(name: Multiplicity): { presence: PartitivePresence; count: PartitiveCount };
171
171
 
172
172
  interface Concept {
173
173
  readonly partitiveRelations: PartitiveRelation[];
174
174
  }
175
175
 
176
- // ── v3 PartitiveRelation (glossarist 0.4.24 — ISO 704:2022 multiplicity + delimiting) ─
177
176
  // Re-exposed here so callers importing from the top-level `glossarist`
178
- // entry get the v3-augmented shape, not the stale d.ts type.
177
+ // entry get the MECE-augmented shape, not the stale d.ts type.
179
178
  export class PartitiveMember extends GlossaristModel {
180
179
  constructor(data?: {
181
180
  ref?: ConceptRef;
182
- multiplicity?: PartitiveMultiplicity;
181
+ presence?: PartitivePresence;
182
+ count?: PartitiveCount;
183
183
  is_delimiting?: boolean;
184
184
  });
185
185
  readonly ref: ConceptRef;
186
- readonly multiplicity: PartitiveMultiplicity;
186
+ readonly presence: PartitivePresence;
187
+ readonly count: PartitiveCount;
187
188
  readonly is_delimiting: boolean;
188
- readonly isConfirmed: boolean;
189
- readonly isPossible: boolean;
190
- readonly isCompulsory: boolean;
189
+ readonly isRequired: boolean;
191
190
  readonly isOptional: boolean;
192
191
  get isDelimiting(): boolean;
193
- toJSON(): { ref: ReturnType<ConceptRef['toJSON']>; multiplicity?: PartitiveMultiplicity; is_delimiting?: boolean };
192
+ toJSON(): { ref: ReturnType<ConceptRef['toJSON']>; presence?: PartitivePresence; count?: PartitiveCount; is_delimiting?: boolean };
194
193
  static fromJSON(data: Record<string, unknown>): PartitiveMember;
195
194
  static identityOf(value: unknown): string;
196
195
  }
@@ -232,11 +231,27 @@ declare module 'glossarist/models' {
232
231
  export const DEFAULT_COMPLETENESS: Completeness;
233
232
  export function isValidCompleteness(value: unknown): value is Completeness;
234
233
 
235
- export type MemberCertainty = 'confirmed' | 'possible';
236
- export const MEMBER_CERTAINTY: { readonly CONFIRMED: 'confirmed'; readonly POSSIBLE: 'possible' };
237
- export const MEMBER_CERTAINTY_VALUES: readonly MemberCertainty[];
238
- export const DEFAULT_MEMBER_CERTAINTY: MemberCertainty;
239
- export function isValidMemberCertainty(value: unknown): value is MemberCertainty;
234
+ export const PARTITIVE_PRESENCE: { readonly REQUIRED: 'required'; readonly OPTIONAL: 'optional' } & { readonly VALUES: readonly ['required', 'optional'] };
235
+ export type PartitivePresence = (typeof PARTITIVE_PRESENCE.VALUES)[number];
236
+ export const PARTITIVE_PRESENCE_VALUES: readonly PartitivePresence[];
237
+ export const DEFAULT_PRESENCE: PartitivePresence;
238
+ export function isValidPresence(value: unknown): value is PartitivePresence;
239
+
240
+ export const PARTITIVE_COUNT: { readonly EXACTLY_ONE: 'exactly_one'; readonly AT_LEAST_ONE: 'at_least_one'; readonly MULTIPLE: 'multiple' } & { readonly VALUES: readonly ['exactly_one', 'at_least_one', 'multiple'] };
241
+ export type PartitiveCount = (typeof PARTITIVE_COUNT.VALUES)[number];
242
+ export const PARTITIVE_COUNT_VALUES: readonly PartitiveCount[];
243
+ export const DEFAULT_COUNT: PartitiveCount;
244
+ export function isValidCount(value: unknown): value is PartitiveCount;
245
+
246
+ export type Multiplicity =
247
+ | 'compulsory' | 'optional' | 'compulsory_multiple'
248
+ | 'optional_multiple' | 'compulsory_at_least_one';
249
+ export const MULTIPLICITY: Record<string, Multiplicity>;
250
+ export const MULTIPLICITY_VALUES: readonly Multiplicity[];
251
+ export const DEFAULT_MULTIPLICITY: Multiplicity;
252
+ export function isValidMultiplicity(value: unknown): value is Multiplicity;
253
+ export function multiplicityFromPair(presence: PartitivePresence, count: PartitiveCount): Multiplicity;
254
+ export function pairFromMultiplicity(name: Multiplicity): { presence: PartitivePresence; count: PartitiveCount };
240
255
 
241
256
  export class TypeSharedPlurality extends GlossaristModel {
242
257
  constructor(data?: {
@@ -255,23 +270,23 @@ declare module 'glossarist/models' {
255
270
  static fromJSON(data: Record<string, unknown>): TypeSharedPlurality;
256
271
  }
257
272
 
258
- // ── v2 PartitiveRelation (glossarist 0.4.24 — ISO 704:2022 multiplicity + delimiting) ─
273
+ // ── PartitiveRelation (glossarist 0.4.26 — MECE presence × count) ─
259
274
 
260
275
  export class PartitiveMember extends GlossaristModel {
261
276
  constructor(data?: {
262
277
  ref?: ConceptRef;
263
- multiplicity?: PartitiveMultiplicity;
278
+ presence?: PartitivePresence;
279
+ count?: PartitiveCount;
264
280
  is_delimiting?: boolean;
265
281
  });
266
282
  readonly ref: ConceptRef;
267
- readonly multiplicity: PartitiveMultiplicity;
283
+ readonly presence: PartitivePresence;
284
+ readonly count: PartitiveCount;
268
285
  readonly is_delimiting: boolean;
269
- readonly isConfirmed: boolean;
270
- readonly isPossible: boolean;
271
- readonly isCompulsory: boolean;
286
+ readonly isRequired: boolean;
272
287
  readonly isOptional: boolean;
273
288
  get isDelimiting(): boolean;
274
- toJSON(): { ref: ReturnType<ConceptRef['toJSON']>; multiplicity?: PartitiveMultiplicity; is_delimiting?: boolean };
289
+ toJSON(): { ref: ReturnType<ConceptRef['toJSON']>; presence?: PartitivePresence; count?: PartitiveCount; is_delimiting?: boolean };
275
290
  static fromJSON(data: Record<string, unknown>): PartitiveMember;
276
291
  static identityOf(value: unknown): string;
277
292
  }
@@ -174,7 +174,8 @@ export interface PartitiveRelationWire {
174
174
 
175
175
  export interface PartitiveMemberWire {
176
176
  uri: string;
177
- multiplicity: 'compulsory' | 'optional' | 'compulsory_multiple' | 'optional_multiple' | 'compulsory_at_least_one';
177
+ presence: 'required' | 'optional';
178
+ count: 'exactly_one' | 'at_least_one' | 'multiple';
178
179
  isDelimiting: boolean;
179
180
  }
180
181
 
@@ -40,14 +40,16 @@
40
40
  import { computed } from 'vue';
41
41
  import {
42
42
  rakeStrokeStyle,
43
- type PartitiveMultiplicity,
43
+ type PartitivePresence,
44
+ type PartitiveCount,
44
45
  type RakeStrokeStyle,
45
46
  } from '../utils/partitive-multiplicity';
46
47
 
47
48
  export interface PartitiveMemberLabeled {
48
49
  uri: string;
49
50
  label: string;
50
- multiplicity: PartitiveMultiplicity;
51
+ presence: PartitivePresence;
52
+ count: PartitiveCount;
51
53
  isDelimiting: boolean;
52
54
  }
53
55
 
@@ -57,7 +59,7 @@ const props = withDefaults(defineProps<{
57
59
  completeness: 'complete' | 'partial';
58
60
  criterion?: string | null;
59
61
  /** Optional per-member tooltip string. */
60
- memberTooltip?: (m: { multiplicity: PartitiveMultiplicity; isDelimiting: boolean }) => string;
62
+ memberTooltip?: (m: { presence: PartitivePresence; count: PartitiveCount; isDelimiting: boolean }) => string;
61
63
  }>(), {
62
64
  criterion: null,
63
65
  });
@@ -142,7 +144,7 @@ const layout = computed(() => {
142
144
  member,
143
145
  x,
144
146
  labelLines,
145
- style: rakeStrokeStyle(member.multiplicity, member.isDelimiting),
147
+ style: rakeStrokeStyle(member.presence, member.count, member.isDelimiting),
146
148
  };
147
149
  });
148
150
 
@@ -198,7 +200,7 @@ function emitNav(uri: string) {
198
200
  }
199
201
 
200
202
  function tooltipFor(member: PartitiveMemberLabeled): string | undefined {
201
- return props.memberTooltip?.({ multiplicity: member.multiplicity, isDelimiting: member.isDelimiting });
203
+ return props.memberTooltip?.({ presence: member.presence, count: member.count, isDelimiting: member.isDelimiting });
202
204
  }
203
205
  </script>
204
206
 
@@ -19,7 +19,7 @@ import { useVocabularyStore } from '../stores/vocabulary';
19
19
  import { getFactory } from '../adapters/factory';
20
20
  import { useI18n, locale } from '../i18n';
21
21
  import { completenessLabel } from '../utils/partitive-relation-styling';
22
- import { multiplicityDefinition } from '../utils/partitive-multiplicity';
22
+ import { presenceLabel, countLabel } from '../utils/partitive-multiplicity';
23
23
  import PartitiveRelationDiagram, {
24
24
  type PartitiveMemberLabeled,
25
25
  } from './PartitiveRelationDiagram.vue';
@@ -85,15 +85,16 @@ function labeledMembers(rel: PartitiveRelationWire): PartitiveMemberLabeled[] {
85
85
  return rel.partitives.map(m => ({
86
86
  uri: m.uri,
87
87
  label: designationFor(m.uri),
88
- multiplicity: m.multiplicity,
88
+ presence: m.presence,
89
+ count: m.count,
89
90
  isDelimiting: m.isDelimiting,
90
91
  }));
91
92
  }
92
93
 
93
94
  /** 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;
95
+ function memberTooltip(m: { presence: 'required' | 'optional'; count: 'exactly_one' | 'at_least_one' | 'multiple'; isDelimiting: boolean }): string {
96
+ const label = `${presenceLabel(m.presence)} · ${countLabel(m.count).toLowerCase()}`;
97
+ return m.isDelimiting ? `Delimiting · ${label}` : label;
97
98
  }
98
99
  </script>
99
100
 
@@ -25,8 +25,8 @@ import { hashSeed, expandParams, portSide, portPoint, idToUriGet } from '../util
25
25
  import { useI18n } from '../i18n';
26
26
 
27
27
  const { t, locale } = useI18n();
28
- import type { Concept, Manifest, GraphEdge, PartitiveRelationWire } from '../adapters/types';
29
- import { rakeStrokeStyle, type PartitiveMultiplicity } from '../utils/partitive-multiplicity';
28
+ import type { Concept, Manifest, GraphEdge, PartitiveRelationWire, PartitiveMemberWire } from '../adapters/types';
29
+ import { rakeStrokeStyle } from '../utils/partitive-multiplicity';
30
30
  import { conceptUri } from '../adapters/model-bridge';
31
31
 
32
32
  const props = defineProps<{
@@ -818,7 +818,7 @@ function drawRakeBundles(
818
818
  const p = pos.get(nid);
819
819
  return p ? { member, pos: p } : null;
820
820
  })
821
- .filter((x): x is { member: { uri: string; multiplicity: PartitiveMultiplicity; isDelimiting: boolean }; pos: {x: number; y: number} } => x !== null);
821
+ .filter((x): x is { member: PartitiveMemberWire; pos: {x: number; y: number} } => x !== null);
822
822
 
823
823
  if (memberPositions.length === 0) continue;
824
824
 
@@ -850,7 +850,7 @@ function drawRakeBundles(
850
850
  toothX,
851
851
  toothY,
852
852
  along,
853
- style: rakeStrokeStyle(member.multiplicity, member.isDelimiting),
853
+ style: rakeStrokeStyle(member.presence, member.count, member.isDelimiting),
854
854
  };
855
855
  });
856
856
 
@@ -1,8 +1,8 @@
1
1
  import { computed, type ComputedRef } from 'vue';
2
2
  import type { Router } from 'vue-router';
3
- // glossarist 0.4.24's top-level d.ts is incomplete for v3 model fields
4
- // (multiplicity + is_delimiting). Import the v3-augmented types from
5
- // glossarist/models (extended by src/adapters/non-verbal/glossarist-augment.d.ts).
3
+ // glossarist-js's top-level d.ts is incomplete for v3 model fields.
4
+ // Import the MECE-augmented types from glossarist/models (extended by
5
+ // src/adapters/non-verbal/glossarist-augment.d.ts).
6
6
  import type { Concept, RelatedConcept, ConceptRef } from 'glossarist';
7
7
  import type {
8
8
  PartitiveRelation as GlsPartitiveRelation,
@@ -23,7 +23,7 @@ import { categorizeRelationship, relationshipLabel, INVERSE_RELATIONSHIPS } from
23
23
  import { langLabel } from '../utils/lang';
24
24
  import { escapeAttr } from '../utils/escape';
25
25
  import { useI18n } from '../i18n';
26
- import type { PartitiveMultiplicity } from '../utils/partitive-multiplicity';
26
+ import { type PartitivePresence, type PartitiveCount } from '../utils/partitive-multiplicity';
27
27
 
28
28
  export interface EdgeDisplay {
29
29
  uri: string;
@@ -192,7 +192,8 @@ export function useConceptEdges(
192
192
  if (!uri || uri === source) return null;
193
193
  return {
194
194
  uri,
195
- multiplicity: readMultiplicity(m),
195
+ presence: readPresence(m),
196
+ count: readCount(m),
196
197
  isDelimiting: readIsDelimiting(m),
197
198
  };
198
199
  })
@@ -216,14 +217,18 @@ export function useConceptEdges(
216
217
  return resolveRefUri({ source: ref.source ?? null, id: ref.id ?? null });
217
218
  }
218
219
 
219
- // glossarist 0.4.24 carries multiplicity + is_delimiting natively on
220
- // PartitiveMember. Read them directly — no migration adapter needed.
221
- function readMultiplicity(m: GlsPartitiveMember): PartitiveMultiplicity {
222
- return m.multiplicity;
220
+ // Read presence + count directly from the glossarist-js model
221
+ // (MECE native, since glossarist 0.4.26).
222
+ function readPresence(m: GlsPartitiveMember): PartitivePresence {
223
+ return m.presence;
224
+ }
225
+
226
+ function readCount(m: GlsPartitiveMember): PartitiveCount {
227
+ return m.count;
223
228
  }
224
229
 
225
230
  function readIsDelimiting(m: GlsPartitiveMember): boolean {
226
- return m.is_delimiting;
231
+ return m.is_delimiting === true;
227
232
  }
228
233
 
229
234
  function resolveRefUri(ref: { source: string | null; id: string | null } | null): string | null {
@@ -1,106 +1,99 @@
1
1
  /**
2
- * ISO 704:2022 §5.5.4.2 — partitive multiplicity + delimiting model.
2
+ * ISO 704:2022 §5.5.4.2 — partitive multiplicity model (MECE).
3
3
  *
4
- * Multiplicity (5 values) encodes part-count semantics:
5
- * - compulsory : exactly one, required
6
- * - optional : zero or one
7
- * - compulsory_multiple : two or more, required
8
- * - optional_multiple : zero or more
9
- * - compulsory_at_least_one : one or more (required, count unknown)
4
+ * Two independent axes:
10
5
  *
11
- * Delimiting (boolean) is orthogonal — a delimiting part behaves like a
12
- * delimiting characteristic in generic relations: it distinguishes the
13
- * comprehensive concept from coordinate concepts. Visual encoding:
14
- * 3× stroke width (4.5 px vs 1.5 px).
6
+ * presence: 'required' | 'optional'
7
+ * Is this part always present?
15
8
  *
16
- * Adding a new multiplicity value = add one entry to PARTITIVE_MULTIPLICITY.
17
- * Renderers consume RakeStrokeStyle via rakeStrokeStyle(); never inspect
18
- * the raw multiplicity string. OCP + MECE + DRY.
9
+ * count: 'exactly_one' | 'at_least_one' | 'multiple'
10
+ * How many instances?
11
+ *
12
+ * 6 combinations, 5 visually distinct (optional + at_least_one
13
+ * collapses to 2-dashed = same as optional + multiple) and the
14
+ * invalid combo (optional, at_least_one) is rejected at construction
15
+ * by glossarist-js.
16
+ *
17
+ * Types are re-exported from glossarist-js — the model is the SSOT.
18
+ * The render-ready `rakeStrokeStyle(presence, count, isDelimiting)`
19
+ * helper is purely a browser-rendering concern; everything else
20
+ * (presence, count, is_delimiting fields, MULTIPLICITY name lookup via
21
+ * multiplicityFromPair, validity via isValidPresence / isValidCount)
22
+ * comes from `glossarist/models`.
19
23
  */
20
24
 
21
- export const PARTITIVE_MULTIPLICITY = {
22
- compulsory: { lines: 1, pattern: 'solid', label: 'Compulsory' },
23
- optional: { lines: 1, pattern: 'dashed', label: 'Optional' },
24
- compulsory_multiple: { lines: 2, pattern: 'solid', label: 'Compulsory multiple' },
25
- optional_multiple: { lines: 2, pattern: 'dashed', label: 'Optional multiple' },
26
- compulsory_at_least_one: { lines: 2, pattern: 'mixed', label: 'At least one' },
27
- } as const;
28
-
29
- export type PartitiveMultiplicity = keyof typeof PARTITIVE_MULTIPLICITY;
30
-
31
- export const PARTITIVE_MULTIPLICITY_VALUES: readonly PartitiveMultiplicity[] =
32
- Object.keys(PARTITIVE_MULTIPLICITY) as PartitiveMultiplicity[];
25
+ import {
26
+ PARTITIVE_PRESENCE,
27
+ PARTITIVE_COUNT,
28
+ multiplicityFromPair,
29
+ isValidPresence,
30
+ isValidCount,
31
+ type PartitivePresence,
32
+ type PartitiveCount,
33
+ } from 'glossarist/models';
33
34
 
34
- export function isPartitiveMultiplicity(value: unknown): value is PartitiveMultiplicity {
35
- return typeof value === 'string' && value in PARTITIVE_MULTIPLICITY;
36
- }
37
-
38
- export type LinePattern = 'solid' | 'dashed' | 'mixed';
35
+ // ── Two independent axes (re-exported SSOT) ────────────────────────────
39
36
 
40
- export interface MultiplicityDefinition {
41
- /** Number of parallel lines used to render the multiplicity. */
42
- readonly lines: 1 | 2;
43
- /** Dash pattern: solid, dashed, or mixed (one solid + one dashed). */
44
- readonly pattern: LinePattern;
45
- /** Human-readable label for legends and badges. */
46
- readonly label: string;
47
- }
37
+ export { PARTITIVE_PRESENCE, PARTITIVE_COUNT };
38
+ export type { PartitivePresence, PartitiveCount };
39
+ export { isValidPresence as isPartitivePresence, isValidCount as isPartitiveCount };
48
40
 
49
- export function multiplicityDefinition(m: PartitiveMultiplicity): MultiplicityDefinition {
50
- return PARTITIVE_MULTIPLICITY[m];
51
- }
41
+ /** ISO 704 name for a (presence, count) pair. Throws on invalid combos. */
42
+ export const partitiveMultiplicityName = multiplicityFromPair;
52
43
 
53
- // ── RakeStrokeStyle — the render-ready form consumed by all renderers ────
44
+ // ── Render-ready stroke style (rendering-only concern) ─────────────────
54
45
 
55
46
  export interface RakeStrokeStyle {
56
- /** 1 or 2 parallel lines. */
57
47
  readonly lineCount: 1 | 2;
58
- /** Primary (always-present) line dash. */
59
48
  readonly primaryDashed: boolean;
60
- /** Secondary line dash (only meaningful when lineCount === 2). */
61
49
  readonly secondaryDashed: boolean;
62
- /** Stroke width in px. 1.5 normal, 4.5 (3×) when is_delimiting. */
63
50
  readonly strokeWidth: number;
64
51
  }
65
52
 
66
53
  export const NORMAL_STROKE_WIDTH = 1.5;
67
- export const DELIMITING_STROKE_WIDTH = 4.5; // 3× normal per ISO 704
54
+ export const DELIMITING_STROKE_WIDTH = 4.5;
68
55
 
69
56
  /**
70
- * Compute the render-ready stroke style for a partitive member.
57
+ * Compute the render-ready stroke style.
71
58
  *
72
- * Pure function — same inputs always yield the same RakeStrokeStyle.
73
- * Consumed by both the sidebar rake diagram (PartitiveRelationDiagram)
74
- * and the sphere rake bundles (RelationSphere.drawRakeBundles).
59
+ * Pure function — consumed by both the sidebar rake diagram
60
+ * (PartitiveRelationDiagram) and the sphere rake bundles
61
+ * (RelationSphere.drawRakeBundles).
62
+ *
63
+ * Derivation:
64
+ * presence=required → primary line is solid
65
+ * presence=optional → primary line is dashed
66
+ * count=exactly_one → 1 line
67
+ * count=multiple → 2 lines, both same style as primary
68
+ * count=at_least_one → 2 lines: primary + dashed ("possibly more")
75
69
  */
76
70
  export function rakeStrokeStyle(
77
- multiplicity: PartitiveMultiplicity,
71
+ presence: PartitivePresence,
72
+ count: PartitiveCount,
78
73
  isDelimiting: boolean,
79
74
  ): RakeStrokeStyle {
80
- const def = PARTITIVE_MULTIPLICITY[multiplicity];
81
- return {
82
- lineCount: def.lines,
83
- primaryDashed: def.pattern === 'dashed',
84
- secondaryDashed: def.pattern === 'dashed' || def.pattern === 'mixed',
85
- strokeWidth: isDelimiting ? DELIMITING_STROKE_WIDTH : NORMAL_STROKE_WIDTH,
86
- };
75
+ const primaryDashed = presence === 'optional';
76
+ const width = isDelimiting ? DELIMITING_STROKE_WIDTH : NORMAL_STROKE_WIDTH;
77
+
78
+ if (count === 'exactly_one') {
79
+ return { lineCount: 1, primaryDashed, secondaryDashed: false, strokeWidth: width };
80
+ }
81
+ // multiple or at_least_one
82
+ const secondaryDashed = count === 'at_least_one' ? true : primaryDashed;
83
+ return { lineCount: 2, primaryDashed, secondaryDashed, strokeWidth: width };
87
84
  }
88
85
 
89
- // ── Migration from glossarist 0.4.20 certainty → ISO 704 multiplicity ────
86
+ // ── Human-readable labels (UI concern) ────────────────────────────────
90
87
 
91
- /**
92
- * glossarist 0.4.20 carries per-member `certainty: 'confirmed' | 'possible'`
93
- * (a v0.4 abstraction that conflated "this member exists" with "we're sure
94
- * about it"). ISO 704:2022 replaces that with the multiplicity enum.
95
- *
96
- * Until glossarist publishes native multiplicity support, this helper maps
97
- * the legacy field to the closest ISO 704 multiplicity:
98
- * - confirmed → compulsory
99
- * - possible → optional
100
- *
101
- * Once glossarist publishes multiplicity natively, the bridge can read
102
- * it directly and this helper can be deleted.
103
- */
104
- export function multiplicityFromCertainty(certainty: 'confirmed' | 'possible' | null | undefined): PartitiveMultiplicity {
105
- return certainty === 'possible' ? 'optional' : 'compulsory';
88
+ export function presenceLabel(p: PartitivePresence): string {
89
+ return p === 'optional' ? 'Optional' : 'Required';
90
+ }
91
+
92
+ export function countLabel(c: PartitiveCount): string {
93
+ switch (c) {
94
+ case 'exactly_one': return 'Exactly one';
95
+ case 'at_least_one': return 'At least one';
96
+ case 'multiple': return 'Multiple';
97
+ default: return '';
98
+ }
106
99
  }
@@ -6,7 +6,7 @@ import { conceptUri } from '../adapters/model-bridge';
6
6
  import { UriRouter } from '../adapters/UriRouter';
7
7
  import type { PartitiveRelationWire, PartitiveMemberWire } from '../adapters/types';
8
8
  import type { PartitiveMember } from 'glossarist/models';
9
- import type { PartitiveMultiplicity } from '../utils/partitive-multiplicity';
9
+ import type { PartitivePresence, PartitiveCount } from '../utils/partitive-multiplicity';
10
10
  import ConceptDetail from '../components/ConceptDetail.vue';
11
11
  import RelationSphere from '../components/RelationSphere.vue';
12
12
  import ShortcutsModal from '../components/ShortcutsModal.vue';
@@ -87,9 +87,10 @@ const partitiveRelationsForSphere = computed<PartitiveRelationWire[]>(() => {
87
87
  if (!ref?.id) return null;
88
88
  const uri = UriRouter.buildConceptUri(m.uriBase, activeRegisterId.value, ref.id);
89
89
  if (uri === sourceUri) return null;
90
- const multiplicity: PartitiveMultiplicity = member.multiplicity;
90
+ const presence: PartitivePresence = member.presence;
91
+ const count: PartitiveCount = member.count;
91
92
  const isDelimiting = member.isDelimiting;
92
- return { uri, multiplicity, isDelimiting };
93
+ return { uri, presence, count, isDelimiting };
93
94
  })
94
95
  .filter((x): x is PartitiveMemberWire => x !== null);
95
96
  if (partitives.length === 0) continue;