json formaat beschreven

This commit is contained in:
2026-09-18 15:52:01 +02:00
parent d351b8e3c8
commit 53c8569973
2 changed files with 943 additions and 0 deletions
+940
View File
@@ -0,0 +1,940 @@
# JSON-export- en importformaat van Racket Wiki
Deze specificatie beschrijft **`racket-wiki-cmap-bundle`, formaatversie 1**,
op basis van het JSON Schema, de implementatie in deze repository en de
drie aangeleverde exports van Racket Wiki 0.2.122:
`modelgids-lts-opzet-cmap.json`, `rws-dingen-cmap.json` en
`rws-lbs-architectuur-cmap.json`.
Een bundel bevat één of meer concept maps (CMaps), gedeelde concepten,
gekoppelde wikipagina's en eventueel hun bijlagen. Het is geen volledige
databaseback-up: gebruikers, instellingen, versiegeschiedenis en aliassen
maken geen deel uit van dit formaat.
## 1. Status en conventies
Het machineleesbare contract staat in
[racket-wiki-cmap-bundle-v1.schema.json](static/schemas/racket-wiki-cmap-bundle-v1.schema.json)
en gebruikt JSON Schema Draft 2020-12. De browser voert daarnaast eigen
controles uit in [interchange.js](static/js/cmap/model/interchange.js).
**MOET**, **MAG** en **BEHOORT** betekenen respectievelijk een eis, een
toegestane mogelijkheid en een aanbeveling. De veldtabellen beschrijven het
schema; aanvullende voorwaarden staan bij de betreffende velden en in § 9.
Verschillen met de huidige uitvoering staan expliciet in § 12. Succesvolle
browservalidatie is dus niet hetzelfde als volledige schemavalidatie.
Het bestand bevat één JSON-object. Gebruik UTF-8, echte JSON-getallen en
booleans; gebruik geen commentaar of afsluitende komma's. Een optioneel veld
MAG ontbreken. `null` is uitsluitend toegestaan waar dat is vermeld.
De volgorde van objectvelden heeft geen betekenis.
### 1.1 Nadere typering
Naast het JSON-basistype gebruikt dit document benoemde typen voor betekenis
en interne structuur. `T[]` betekent een array van elementen van type `T`;
`T of null` staat expliciet `null` toe. `Record<K, V>` is een JSON-object
waarvan de veldnamen sleutels van type `K` zijn en de waarden type `V` hebben.
Een lege array in een voorbeeld bewijst niet welk elementtype is toegestaan.
De tabellen met **schema** als aanduiding beschrijven het bestaande contract.
Tabellen met **model/export** beschrijven de huidige modelrepresentatie,
waar nodig aangevuld met waarnemingen uit de drie aangeleverde exports. Een
waargenomen waarde is geen uitputtende opsomming van toegestane waarden.
Een gewenste semantische wijziging wordt afzonderlijk aangeduid en is geen
bewering dat de huidige software die wijziging al uitvoert.
### 1.2 Verbindingszin: betekenis en huidige representatie
Het beoogde domeinuitgangspunt is: **een verbindingszin drukt een relatie
tussen concepten uit en is zelf een concept**. Die betekenis is onafhankelijk
van de kaart waarop zij wordt getoond. De plaatsing, opmaak en getekende
lijnstukken zijn wel kaartgebonden.
De huidige export vertegenwoordigt dit uitgangspunt nog niet volledig.
Daarin is `omvat` een item met `kind: "phrase"`, `id: 2` en `conceptId: null`.
De tekst staat op het item, niet in de gedeelde `concepts`-tabel. Het model
`ConceptMapPhrase` bewaart dit eveneens zo. De eerdere formulering
“een verbindingszin is kaartlokaal” was daarom te algemeen: **de huidige
opslag van de verbindingszin is kaartlokaal; haar bedoelde betekenis is dat niet**.
De code kent daarnaast een `ConceptRelation` met bronconcept, doelconcept
en een tekstueel label (§ 5.3). Dat is een semantische relatie, maar maakt
het label nog niet tot een zelfstandig gedeeld concept. Geen van de drie
exports bevat `conceptRelations`, `conceptOwnerships` of een `relationId`
op de connectoren. Alle phrase-items hebben `conceptId: null`.
De editorfunctie `connectWithPhrase` maakt een phrase en connectoren aan,
zonder daarbij een `ConceptRelation` te registreren.
Om het domeinuitgangspunt volledig te ondersteunen, zouden verbindingszinnen
een gedeelde conceptidentiteit moeten hebben en zouden semantische relaties
die identiteit moeten gebruiken. Het huidige formaat bevat daarvoor geen
expliciete rolverwijzing naar het verbindingsconcept. Alleen een `conceptId`
invullen op een `phrase` volstaat niet: het huidige phrase-model bewaart
die koppeling niet. Dit is een verschil tussen het bedoelde domeinmodel en
de implementatie, geen wijziging die deze documentatie in het formaat aanbrengt.
### 1.3 Verbindingszinnen in model en database
Verbindingszinnen zijn **wel onderdeel van de persistent opgeslagen wikidata**.
De opslagcode behandelt ze als volgt:
| Laag | Huidige representatie |
| --- | --- |
| JavaScript-model | `ConceptMapPhrase`, met eigen lokale `id`, `label` en `synopsis`. |
| CMap-document | Een object in `document.items` met `kind: "phrase"`; connectoren verwijzen naar dit item via `sourceId`/`targetId`. |
| Database | Het phrase-item, inclusief tekst, blijft staan in de JSONB-kolom `concept_maps.document`. |
| Gedeeld conceptregister | `concept_definitions` bevat de gedeelde conceptinhoud. Phrases worden door `concept-placement?` expliciet uitgesloten van conceptplaatsingen. |
`concept-map-storage-document` verwijdert gedeelde inhoud uit gewone
conceptplaatsingen, maar laat phrase-items ongewijzigd. `create-concept-map!`
en `update-concept-map!` schrijven dit document naar PostgreSQL. Ook de
migraties die gedeelde conceptinhoud uit items verwijderen, behouden
expliciet de tekst van phrases.
De conclusie is daarom: **persistent in het kaartmodel, maar niet als
zelfstandig gedeeld concept gemodelleerd**. De tekst staat niet op de
connector zelf en wordt niet uitsluitend door de presentatielaag bijgehouden.
Een apart `ConceptRelation`-object in de JavaScript-code verandert dit niet
automatisch: het aanmaken van een phrase vult die semantische relatietabel
momenteel niet.
Deze vaststelling is gebaseerd op de opslagcode en migraties in deze
repository en op de drie exports; er is geen rechtstreekse inspectie van
de actieve database uitgevoerd.
## 2. Structuur van de bundel
| Veld | Type | Verplicht | Betekenis |
| --- | --- | --- | --- |
| `$schema` | string | Nee | Verwijzing naar het schema; de exporter schrijft `/schemas/racket-wiki-cmap-bundle-v1.schema.json`. |
| `format` | string | Ja | Exact `racket-wiki-cmap-bundle`. |
| `formatVersion` | integer | Ja | Exact `1`. Andere versies worden afgewezen. |
| `exportedAt` | string | Nee | Exporttijdstip in `date-time`-notatie, bijvoorbeeld `2026-09-18T12:00:00.000Z`. |
| `generator` | string | Nee | Naam van de producerende toepassing; standaard `Racket Wiki`. |
| `rootCmapSlug` | slug | Ja | Slug van de CMap die na import wordt geopend; MOET voorkomen in `cmaps`. |
| `cmaps` | array van CMap-objecten | Ja | Ten minste één CMap; zie § 5. |
| `concepts` | array van conceptobjecten | Ja | Gedeelde conceptinhoud; zie § 4. MAG leeg zijn. |
| `pages` | array van paginaobjecten | Ja | Meegeleverde wikipagina's; zie § 7. MAG leeg zijn. |
| `missing` | object | Nee | Niet geladen afhankelijkheden; zie § 8. |
De exporter schrijft al deze velden. Op het hoofdniveau zijn andere velden
volgens het schema niet toegestaan.
## 3. Identificatie en verwijzingen
### 3.1 Slug
Een slug begint met een Unicode-letter of -cijfer. Daarna zijn ook `.`, `_`
en `-` toegestaan. De totale lengte is 1 tot en met 120 tekens, volgens
deze JavaScript-reguliere expressie met Unicode-modus:
```text
^[\p{L}\p{N}][\p{L}\p{N}._-]{0,119}$
```
Dit geldt voor CMap-slugs en voor het sluggedeelte van paginaverwijzingen.
Een CMap-slug bevat geen dubbele punt. Identiteiten en verwijzingen moeten
exact overeenkomen; de bundelvalidator vergelijkt ze hoofdlettergevoelig.
### 3.2 Paginaverwijzing
Een paginaverwijzing heeft de vorm `slug` of `namespace:slug`. Beide delen
voldoen aan de slugregels, met maximaal 80 tekens voor de namespace.
Voorbeelden: `inleiding`, `proces:ontwerp` en `cmap:architectuur`.
Het is een wiki-adres, geen URL of bestandsnaam.
### 3.3 Concept-id
Een concept-id is een string met één van de volgende vormen:
| Vorm | Regel | Voorbeeld |
| --- | --- | --- |
| UUID | Hexadecimale groepen van 8-4-4-4-12 tekens; hoofdletters toegestaan. | `24d27086-9f8b-4b57-a7fb-47b513277555` |
| Tijdelijke id | `new:` gevolgd door 1–120 ASCII-tekens; eerst een letter of cijfer, daarna ook `.`, `_` en `-`. | `new:proces` |
De UUID-controle toetst de tekstvorm, niet de UUID-versie. Gebruik bij voorkeur
kleine letters. Externe generators MOGEN tijdelijke id's gebruiken. De server
zet nieuwe identiteiten bij opslag om naar UUID's en kan concepten op basis
van een reeds bestaande naam aan een bestaande identiteit koppelen (§ 11).
### 3.4 Lokale item-id
Een `document.items[].id` is een integer die uniek is binnen zijn CMap.
Een andere CMap MAG dezelfde item-id gebruiken. Een item-id identificeert
een plaatsing of verbindingszin; een concept-id identificeert gedeelde inhoud.
Connectoren verwijzen naar lokale item-id's, nooit rechtstreeks naar concept-id's.
### 3.5 Benoemde waardetypen
| Type | JSON-basistype | Nadere typering |
| --- | --- | --- |
| `Slug` | string | Adres volgens § 3.1. |
| `PageReference` | string | `slug` of `namespace:slug`, volgens § 3.2. |
| `ConceptId` | string | UUID of tijdelijke id volgens § 3.3. |
| `ItemId` | integer | Verwijzing binnen één diagram; § 3.4. |
| `ConnectorId` | integer | Identiteit binnen de connectoren van één diagram. |
| `RelationId` | string | Niet-lege identiteit van een `ConceptRelation`; het model legt geen UUID-patroon op. |
| `ContextKey` | string | `root` of de `id` van een interne kaartreferentie, bijvoorbeeld `cmap-7`. |
| `CssColor` | string | CSS-kleur; waargenomen zijn hexkleuren zoals `#edf7e8`, de korte vorm `#333` en `transparent`. Geen kleurenenum in het bundelschema. |
| `FontSize` | number of string | Huidige model/exportwaarde: getal of CSS-lettergrootte, bijvoorbeeld `"11pt"`, `"9pt"`, `"0.84em"`. Het schema staat op items nu alleen `number` toe. |
| `FontFamily` | string | CSS-lettertypefamilie of lijst daarvan, bijvoorbeeld `"Arial, Helvetica, sans-serif"`. |
| `FontWeight` | string | CSS-gewicht als tekst, in de export `"400"` of `"700"`; dit zijn geen JSON-getallen. |
| `FontStyle` | string | CSS-letterstijl; de voorbeelden bevatten `"normal"` en `"italic"`. Het schema legt geen enum op. |
| `HttpUrl` | string | Volledige URL met protocol `http:` of `https:`. |
| `UploadUrl` | string | Lokale URL die begint met `/uploads/`. |
| `Base64` | string | Gecodeerde bestandsbytes volgens § 7.2. |
Voor `FontSize` verwerkt de lettergroottehelper getallen en eenheden `pt`,
`px`, `em` en `%` bij omzetting naar punten. Dat is geen volledige
CSS-validatie door de bundelvalidator. Behoud bij overdracht de oorspronkelijke
waarde en eenheid; `"0.84em"` mag niet zonder betekenisverlies `0.84` worden.
## 4. Gedeelde concepten: `concepts[]`
| Veld | Type | Verplicht | Betekenis |
| --- | --- | --- | --- |
| `id` | concept-id | Ja | Unieke identiteit binnen de bundel. |
| `label` | string | Ja | Naam; MOET na trimmen niet leeg zijn. |
| `synopsis` | string | Nee | Korte beschrijving. |
| `aspects` | array van strings | Nee | Aspecten van het concept. |
| `tags` | array; model: `ConceptTag[]` | Nee | Concepttags; zie § 4.1. |
| `descriptionPageSlug` | string of null; niet-leeg: `PageReference` | Nee | Paginaverwijzing naar conceptuitleg. |
| `pageSlug` | string of null; niet-leeg: `PageReference` | Nee | Gekoppelde wikipagina. |
| `cmapSlug` | string of null; niet-leeg: `Slug` | Nee | Gekoppelde CMap. |
| `externalUrl` | `HttpUrl` of null | Nee | Volledige HTTP- of HTTPS-URL. Een lege string is ongeldig. |
| `imageSource` | string of null | Nee | Afbeeldingsbron zoals opgeslagen door het conceptmodel. |
Andere conceptvelden zijn volgens het schema niet toegestaan. Niet-lege
paginaverwijzingen en CMap-verwijzingen MOETEN aan § 3 voldoen. Gebruik voor
een ontbrekende verwijzing bij voorkeur weglaten of `null`.
Conceptnamen MOETEN uniek zijn na trimmen en omzetting naar kleine letters
(`toLocaleLowerCase()` in de validator). Ieder concept MOET op ten minste
één CMap in de bundel als niet-`phrase`-item geplaatst zijn. Hetzelfde concept
MAG meerdere plaatsingen hebben en op meerdere CMaps voorkomen.
Het schema laat de inhoud van `tags` vrij. Het huidige model gebruikt
objecten met `type` en `value`, bijvoorbeeld
`{"type":"person","value":"Hans"}` of
`{"type":"label","value":"ontwerp"}`. Strings worden omgezet naar
tags van type `label`; bij objecten kan `name` als alternatief voor `value`
worden gelezen. Paginatags (§ 7) zijn uitsluitend strings.
### 4.1 Concepttag: `ConceptTag`
| Veld | Type in model/export | Betekenis |
| --- | --- | --- |
| `type` | string | Tagcategorie, bijvoorbeeld `label` of `person`. Het model bewaart ook andere niet-lege categorienamen; dit is geen gesloten enum. |
| `value` | string | Tagwaarde, bijvoorbeeld een label of persoonsnaam. |
Het model schrijft beide velden. Bij invoer wordt een ontbrekend of leeg
`type` aangevuld met `label`. `value` en de compatibiliteitsinvoer `name`
worden getrimd. De Modelgids-export bevat alleen lege concepttagarrays;
deze elementtypering is daarom ontleend aan het conceptmodel.
De aanvullende export `rws-dingen-cmap.json` bevat wel gevulde tags,
bijvoorbeeld `{"type":"person","value":"Hans"}`. Daarmee bevestigt die
export de objectstructuur. `aspects` bevat daar onder meer `"TODO"`,
`"Todo"` en `"todo"`: het is een array van vrije strings, geen enum en geen
lijst van `ConceptTag`-objecten. Het model schrijft deze strings niet naar
één vaste schrijfwijze om.
Bij opslag in het gedeelde conceptregister bewaart de huidige server via
`concept-content` uitsluitend concepttags met `type: "person"`. Andere
tagtypen kunnen dus door het JSON Schema en het JavaScript-model worden
geaccepteerd zonder na opslag behouden te blijven. Voor gewone aspecten
staat het afzonderlijke veld `aspects` ter beschikking.
## 5. CMaps: `cmaps[]`
| Veld | Type | Verplicht | Betekenis |
| --- | --- | --- | --- |
| `slug` | slug | Ja | Uniek CMap-adres binnen de bundel. |
| `title` | string | Ja | Titel; MOET na trimmen niet leeg zijn. |
| `document` | object | Ja | Diagramdocument volgens onderstaande tabel. |
Andere velden op dit CMap-object zijn volgens het schema niet toegestaan.
### 5.1 Diagramdocument
| Veld | Type | Verplicht | Betekenis |
| --- | --- | --- | --- |
| `schemaVersion` | integer | Nee | Versie van het diagrammodel; het huidige model gebruikt standaard `2`. Dit is onafhankelijk van `formatVersion`. |
| `metadata` | object | Nee | CMap-metadata; zie hieronder. |
| `derivedView` | object | Nee | Verwijzing naar een afgeleide weergave; zie § 5.2. |
| `concepts` | array van `{ "id": concept-id }` | Ja | Verwijzingen naar de gedeelde concepttabel; geen conceptinhoud. Id's MOETEN binnen deze array uniek zijn. |
| `items` | array van items | Ja | Plaatsingen en verbindingszinnen; zie § 6. |
| `connectors` | array van connectoren | Ja | Diagramverbindingen; zie § 6.2. |
| `conceptMaps` | array; model: `MapReference[]` | Nee | Interne kaartreferenties volgens § 6.4; leeg in de Modelgids-export en gevuld in de twee aanvullende exports. |
| `conceptRelations` | model: `ConceptRelation[]` | Nee | Semantische relaties tussen concepten; modeluitbreiding volgens § 5.3. |
| `conceptOwnerships` | model: `ConceptOwnership[]` | Nee | Semantische ouder-kindrelaties; modeluitbreiding volgens § 5.3. |
| `viewport` | object | Nee | Toegestaan door het schema; het huidige model bewaart dit niet bij import. |
Een lege CMap is toegestaan: de drie verplichte arrays mogen leeg zijn.
Gebruik bij voorkeur een `document.concepts`-verwijzing voor ieder geplaatst
concept. De importer verzamelt ook concept-id's rechtstreeks uit de items.
Het huidige model gebruikt de volgende metadata:
| Veld | Gebruikelijk type | Betekenis |
| --- | --- | --- |
| `namespace` | string | Namespace van de kaart. |
| `tags` | array van strings | Kaarttags. |
| `summary` | string | Samenvatting. |
| `explanationPageSlug` | string | Paginaverwijzing naar kaartuitleg, of `""` indien afwezig. |
Het schema staat extra velden toe op `document` en `metadata`. Dat garandeert
niet dat de importer onbekende velden bewaart: het diagrammodel wordt opnieuw
opgebouwd. Modeluitbreidingen zoals `conceptRelations` en `conceptOwnerships`
vallen onder deze open documentstructuur. Hun huidige modeltypen worden
hieronder beschreven; het bundelschema specificeert of valideert die
interne structuur niet.
### 5.2 Afgeleide CMap: `derivedView`
| Veld | Type | Verplicht | Betekenis |
| --- | --- | --- | --- |
| `sourceCmapSlug` | slug | Ja | Bron-CMap van de afgeleide weergave. |
| `rootItemId` | integer | Ja | Lokaal item in de bron-CMap dat de wortel van de weergave vormt. |
Extra velden zijn hier niet toegestaan. Voor een bruikbare afgeleide weergave
moeten bronkaart en wortelitem beschikbaar zijn. De exporter probeert de
bronkaart ook bij exportdiepte `0` mee te nemen. De browservalidator controleert
het bestaan van deze bron en dit item niet.
De export `rws-lbs-architectuur-cmap.json` bevat vijf concrete afgeleide
kaarten (§ 13.3). Hun `concepts`, `items` en `connectors` zijn lege arrays:
de bronkaart levert de diagraminhoud. Een lege lokale itemarray betekent bij
een `derivedView` dus niet dat de zichtbare kaart geen inhoud heeft.
### 5.3 Semantische relaties en eigenaarschap in het model
`ConceptRelation` is het elementtype van `document.conceptRelations`:
| Veld | Type in model/export | Betekenis |
| --- | --- | --- |
| `id` | `RelationId` | Identiteit van de relatie, onafhankelijk van een lokale connector-id. |
| `sourceConceptId` | `ConceptId` | Bronconcept. |
| `targetConceptId` | `ConceptId` | Doelconcept. |
| `label` | string | Relatietekst; standaard `""`. Er is hier nog geen verwijzing naar een concept dat de verbindingszin vertegenwoordigt. |
| `tags` | `ConceptTag[]` | Relatietags; standaard `[]`. |
Het model schrijft alle vijf velden. Voor constructie zijn een niet-lege
id, bron en doel nodig. De repository controleert dat bron en doel in de
concepten van het opgebouwde kaartmodel voorkomen. Dit is een latere
modelcontrole, niet een controle van `validateBundle`.
`ConceptOwnership` is het elementtype van `document.conceptOwnerships`:
| Veld | Type in model/export | Betekenis |
| --- | --- | --- |
| `parentConceptId` | `ConceptId` | Semantisch ouderconcept. |
| `childConceptId` | `ConceptId` | Semantisch kindconcept. |
Beide velden zijn nodig. De repository vereist bestaande, verschillende
concepten en wijst cyclisch eigenaarschap af. Zij bewaart één ouder per kind.
Dit eigenaarschap is iets anders dan de plaatsing onder `parentSubmapId`.
Een verbindingszin met het label `omvat` creëert niet automatisch een
`ConceptOwnership`. Beide arrays ontbreken in de Modelgids-export.
## 6. Diagramitems en connectoren
### 6.1 Items: `cmaps[].document.items[]`
| Veld | Type volgens schema | Nadere typering in model/export | Verplicht volgens schema |
| --- | --- | --- | --- |
| `id` | integer | `ItemId`, lokale identiteit van de plaatsing. | Ja |
| `x`, `y` | number | Diagramcoördinaten; negatieve waarden en decimalen toegestaan. | Ja |
| `kind` | enum van strings | `concept`, `page`, `submap` of `phrase`; standaard `concept`. | Nee |
| `conceptId` | concept-id | `ConceptId` bij conceptplaatsingen; bij `phrase` exporteert het model `null`. | Tenzij `kind = "phrase"` |
| `label` | string | Tekst van de huidige phrase-representatie; conceptplaatsingen halen de naam uit `concepts`. | Bij `kind = "phrase"` |
| `width`, `height` | number | Afmetingen in diagramcoördinaten. | Nee |
| `parentSubmapId` | integer of null | `ItemId` van de bovenliggende submap, of `null`. | Nee |
| `hidden` | boolean | Niet bewaard door het huidige model; dit gebruikt `hiddenContexts`. | Nee |
| `backgroundColor`, `borderColor`, `textColor` | string | `CssColor`. | Nee |
| `fontFamily` | string | `FontFamily`. | Nee |
| `fontWeight` | string | `FontWeight`. | Nee |
| `fontStyle` | string | `FontStyle`. | Nee |
| `fontSize`, `synopsisFontSize` | number | `FontSize`; in de Modelgids-export strings met eenheid. | Nee |
| `synopsisFontWeight` | string | `FontWeight` voor de synopsis. | Nee |
| `synopsisFontStyle` | string | `FontStyle` voor de synopsis. | Nee |
| `submapBackgroundColor`, `submapBorderColor` | string | `CssColor of null`; in de Modelgids-export steeds `null`. | Nee |
De laatste twee verschillen — lettergroottes als strings en nullable
submapkleuren — worden door de browser geaccepteerd, maar vallen buiten de
huidige veldtypen van het schema (§ 12).
Een `phrase`-item is de huidige diagramrepresentatie van een verbindingszin;
het vereist in versie 1 geen gedeeld conceptobject. Dit beschrijft de opslag,
niet de bedoelde conceptsemantiek (§ 1.2). Voor schema-conforme versie-1-invoer
BEHOORT `conceptId` op een `phrase` weggelaten te worden; de huidige exporter
schrijft daar zelf `null`.
Bij conceptplaatsingen wordt gedeelde inhoud zoals `label`, `synopsis`,
`aspects`, `tags` en links door de exporter van het item verwijderd.
Die inhoud staat uitsluitend in de gedeelde concepttabel.
Extra itemvelden zijn toegestaan door het schema. Hun modeltypen staan in
§ 6.3–6.5. Behoud deze velden bij bewerking van een bestaande export om
diagramgedrag en opmaak zo veel mogelijk te behouden.
### 6.2 Connectoren: `cmaps[].document.connectors[]`
| Veld | Type | Verplicht volgens schema | Betekenis |
| --- | --- | --- | --- |
| `id` | `ConnectorId` (integer) | Nee | Lokale connector-id; de huidige importer vereist deze wel. |
| `sourceId` | `ItemId` (integer) | Ja | Bestaande item-id in dezelfde CMap. |
| `targetId` | `ItemId` (integer) | Ja | Bestaande item-id in dezelfde CMap. |
| `hasArrow` | boolean | Nee | Pijlpunt; standaard `true` in het model. |
| `relationId` | model: `RelationId` (string) | Nee, open uitbreiding | Koppeling naar een semantische relatie; ontbreekt in de Modelgids-export. |
| `lineColor` | model: `CssColor` (string) | Nee, open uitbreiding | Lijnkleur; standaard `"#333"`. |
| `lineWidth` | model: number | Nee, open uitbreiding | Lijndikte; standaard `2`. |
Extra velden zijn toegestaan. Lever iedere
connector met een unieke integer-`id` binnen zijn kaart aan voor succesvolle
import. Connector-id's en item-id's hebben afzonderlijke verzamelingen.
Een connector is één getekend lijnstuk. Bij een constructie
`bronconcept → phrase → doelconcept` zijn er twee connectoren voor één
semantische uitspraak. Een phrase kan meerdere inkomende of uitgaande
connectoren hebben. `hasArrow: false` onderdrukt de pijlpunt; `sourceId` en
`targetId` blijven de geregistreerde bron en het doel van dat lijnstuk.
De exporter verwijdert connectoren waarvan een eindpunt niet meer bestaat.
De importer wijst zulke connectoren af als ze toch in het bestand staan.
### 6.3 Aanvullende itemvelden
Deze velden vallen onder `additionalProperties: true` van het itemschema.
Onderstaande typen beschrijven het huidige model en de editor; de
bundelvalidator dwingt deze nadere typering niet af.
| Veld | Type in model/export | Betekenis en waarneming in de Modelgids-export |
| --- | --- | --- |
| `synopsis` | string | Phrase-eigen toelichting in de huidige representatie; bij `omvat` is dit `""`. Voor concepten staat deze inhoud in de gedeelde tabel. |
| `parentCmapLink` | boolean | Markering van een plaatsing als terugkoppeling naar een bovenliggende kaart; hier overal `false`. |
| `groupId` | string of null | Groepsidentiteit voor plaatsingen; hier `null`. Door de editor gebruikte vorm: `group-<nummer>`. |
| `childMap` | string of null | Naam van een onderliggende kaart/submap; hier `null`. Dit is geen ingebed diagramdocument. |
| `submapDepth` | integer | Nestingsniveau in de editor, met `0` voor het hoofdniveau; hier overal `0`. Het schema specificeert dit veld niet. |
| `expanded` | boolean | Of de submap uitgeklapt is; hier `false`. |
| `submapInitialized` | boolean | Of de submapinhoud is geïnitialiseerd; hier `false`. |
| `separateMap` | boolean | Of de submap een afzonderlijke interne kaartreferentie heeft; hier `false`. |
| `mapReference` | `MapReference` of null | Interne kaartreferentie volgens § 6.4; hier `null`. |
| `hiddenContexts` | `ContextKey[]` | Contexten waarin de plaatsing verborgen is; hier `[]`. |
| `layouts` | `Record<ContextKey, Layout>` | Opgeslagen plaatsing en opmaak per context; hier steeds een object met sleutel `root`. Zie § 6.5. |
| `autoWidth`, `autoHeight` | boolean | Automatische bepaling van breedte respectievelijk hoogte. Beide blijven booleans, ook als `width` en `height` zijn ingevuld. |
| `synopsisTextColor` | `CssColor` | Tekstkleur van de synopsis. |
| `synopsisFontFamily` | `FontFamily` | Lettertypefamilie van de synopsis. |
Alleen `null` in een export toont niet welk niet-null type mogelijk is.
De typering van `groupId`, `childMap` en `mapReference` is daarom aangevuld
vanuit de editorcode. De twee aanvullende exports bevestigen `childMap` als
string en `mapReference` als object. Ze bevatten ook integerwaarden voor
`parentSubmapId`, submapdieptes groter dan nul en daadwerkelijke kleuren voor
de nullable submapkleurvelden. `groupId` blijft in alle drie voorbeelden
`null`; `hiddenContexts` blijft steeds een lege array.
### 6.4 Interne kaartreferentie: `MapReference`
Dit is het objecttype voor `item.mapReference` en de elementen van
`document.conceptMaps`. De submapcontroller maakt de volgende structuur:
| Veld | Type in model/export | Betekenis |
| --- | --- | --- |
| `id` | string | Interne referentie-id, door de editor opgebouwd als `cmap-<ItemId>`. Ook bruikbaar als `ContextKey`; geen opgeslagen CMap-slug. |
| `title` | string | Titel van de interne kaart. |
| `rootItemId` | `ItemId` | Wortelitem binnen het diagram. |
| `itemIds` | `ItemId[]` | Items die onder die wortel vallen. |
Deze structuur verschilt van `derivedView`: een afgeleide opgeslagen kaart
gebruikt een `sourceCmapSlug` om een andere opgeslagen CMap aan te wijzen.
In de Modelgids-export is `conceptMaps` leeg en is ieder `mapReference`
`null`; de structuur hierboven is ontleend aan de model- en controllercode.
De twee aanvullende exports bevatten deze structuur daadwerkelijk, onder
meer de referentie `cmap-23` naar de submap `RWS TODO` (§ 13.2).
`itemIds` is een opgeslagen lijst van lokale verwijzingen. De huidige
bundelvalidator controleert die lijst niet op volledigheid of overeenstemming
met `parentSubmapId`. Ook in de voorbeelden zijn zulke lijsten niet altijd
actueel. Gebruik ze daarom niet als de enige bron voor de submapstructuur;
het huidige model bepaalt afstammelingen via `parentSubmapId`.
### 6.5 Layout per context: `Layout`
De export bevat bijvoorbeeld `items[0].layouts.root`. `root` is hier een
objectsleutel van type `ContextKey`, geen vast veld in elk layoutobject.
Iedere waarde in `layouts` heeft de volgende structuur:
| Veld | Type in model/export | Voorbeeld uit de Modelgids-export |
| --- | --- | --- |
| `x`, `y` | number | `33`, `75`, `152.66666666666669` |
| `width`, `height` | number | `380`, `76` |
| `backgroundColor`, `borderColor`, `textColor` | `CssColor` | `"#edf7e8"`, `"#57834a"`, `"#222222"`; bij de phrase ook `"transparent"` als randkleur |
| `fontFamily` | `FontFamily` | `"Arial, Helvetica, sans-serif"` |
| `fontSize` | `FontSize` | `"11pt"` |
| `fontWeight` | `FontWeight` | `"700"` |
| `fontStyle` | `FontStyle` | `"normal"` |
| `synopsisTextColor` | `CssColor` | `"#4d4d4d"` |
| `synopsisFontFamily` | `FontFamily` | `"Arial, Helvetica, sans-serif"` |
| `synopsisFontSize` | `FontSize` | `"9pt"`; bij de phrase `"0.84em"` |
| `synopsisFontWeight` | `FontWeight` | `"400"`; bij de phrase `"700"` |
| `synopsisFontStyle` | `FontStyle` | `"normal"` |
Dit zijn de velden die de layoutcontroller vastlegt. Het bundelschema legt
geen verplichte deelvelden of typen voor `Layout` vast. De directe
itemvelden beschrijven de plaatsing; de contextlayout bevat een opgeslagen
weergave daarvan. Bij openen van een context past de editor de bijbehorende
layout toe. Wie coördinaten of opmaak in een bestaande export wijzigt,
moet dus ook de relevante contextlayout consistent aanpassen.
De aanvullende exports bevatten behalve `root` onder meer `cmap-23`,
`cmap-26` en `cmap-89` als contexten. Ook afmetingen kunnen decimalen bevatten,
bijvoorbeeld `width: 180.888916015625`. Gebruik voor alle vier geometrievelden
dus `number`, niet `integer`. Een contextkey is lokaal aan zijn diagram;
dezelfde tekst in twee verschillende CMaps maakt de contexten niet identiek.
## 7. Wikipagina's en bijlagen
### 7.1 Pagina: `pages[]`
| Veld | Type | Verplicht | Betekenis |
| --- | --- | --- | --- |
| `reference` | paginaverwijzing | Ja | Uniek pagina-adres binnen de bundel, inclusief eventuele namespace. |
| `title` | string | Ja | Titel; MOET na trimmen niet leeg zijn. |
| `markdown` | string | Ja | Actuele Markdown-bron; MAG leeg zijn. |
| `tags` | array van strings | Ja | Paginatags; MAG leeg zijn. |
| `attachments` | array van bijlagen | Nee | Ingebedde bestanden; ontbrekend betekent geen meegeleverde bijlagen. |
Andere paginavelden zijn volgens het schema niet toegestaan. Bundels van vóór
de toevoeging van bijlagen blijven geldig zonder wijziging van `formatVersion`.
### 7.2 Bijlage: `pages[].attachments[]`
| Veld | Type | Verplicht | Betekenis |
| --- | --- | --- | --- |
| `url` | `UploadUrl` (string) | Ja | Oorspronkelijke lokale URL, beginnend met `/uploads/`; uniek binnen de pagina. |
| `name` | string | Ja | Niet-lege bestandsnaam. |
| `mimeType` | string | Ja | Niet-leeg MIME-type, bijvoorbeeld `image/png`. |
| `contentBase64` | `Base64` (string) | Ja | Bestandsbytes als standaard Base64. |
Andere bijlagevelden zijn niet toegestaan. Base64 gebruikt `A–Z`, `a–z`,
`0–9`, `+`, `/` en waar nodig afsluitende `=`-padding. Witruimte, regeleinden
en een `data:`-prefix zijn niet toegestaan. Een lege string vertegenwoordigt
een leeg bestand en wordt door de bundelvalidator geaccepteerd.
Bij import worden bestanden opnieuw geüpload onder de betreffende pagina.
De oorspronkelijke `url` dient als vervangsleutel: voorkomens in de Markdown
worden vervangen door de nieuwe upload-URL. Bestandsnamen en URL's hoeven na
import dus niet identiek te zijn aan de export.
## 8. Ontbrekende afhankelijkheden: `missing`
| Veld | Type | Verplicht | Betekenis |
| --- | --- | --- | --- |
| `cmaps` | array van slugs | Nee | CMaps waarvoor het laden tijdens export is mislukt. |
| `pages` | array van niet-lege strings | Nee | Paginaverwijzingen waarvoor export is mislukt. |
Extra velden zijn niet toegestaan. De exporter schrijft beide arrays, ook
als ze leeg zijn. Een mislukte bijlage-export laat de hele betreffende pagina
uit `pages` weg en vermeldt haar verwijzing in `missing.pages`.
`missing` bevat geen vervangende inhoud. De importer maakt voor deze records
geen lege pagina's of kaarten aan en probeert ze niet alsnog op te halen.
Een CMap die uitsluitend wegens de gekozen dieptelimiet niet is meegenomen,
wordt niet als ontbrekend vermeld.
## 9. Aanvullende validatieregels
Naast de veldtypen gelden de volgende controles in `validateBundle`:
1. CMap-slugs, pagina-adressen en gedeelde concept-id's MOETEN binnen hun
eigen verzameling uniek zijn. Conceptnamen voldoen bovendien aan § 4.
2. `rootCmapSlug` MOET exact naar een meegeleverde CMap verwijzen.
3. Iedere `document.concepts[].id` en iedere niet-`phrase`-`conceptId` MOET
bestaan in de gedeelde concepttabel.
4. Elk gedeeld concept MOET ten minste één niet-`phrase`-plaatsing hebben.
Een vermelding in alleen `document.concepts` is onvoldoende.
5. Item-id's MOETEN per kaart uniek zijn; alle connector-eindpunten MOETEN
binnen dezelfde kaart bestaan.
6. Iedere niet-lege `pageSlug`, `descriptionPageSlug` en
`metadata.explanationPageSlug` MOET voorkomen als `pages[].reference`
of als exacte verwijzing in `missing.pages`.
7. Bijlage-URL's MOETEN per pagina uniek zijn en hun inhoud MOET aan de
Base64-regel voldoen.
Een gekoppelde `cmapSlug` hoeft niet in `cmaps` of `missing.cmaps` voor te
komen: export kan bewust op een bepaalde diepte stoppen. Ook extra pagina's
zonder verwijzing vanuit een concept of kaart zijn toegestaan.
Er is geen controle dat iedere upload-URL in Markdown een bijlage heeft,
noch dat iedere meegeleverde bijlage daadwerkelijk wordt gebruikt. Voor een
zelfstandige overdracht BEHOORT de generator alle benodigde lokale bestanden
mee te leveren.
## 10. Selectie tijdens export
De normale JSON-export slaat eerst openstaande kaartwijzigingen op en neemt
de gekozen wortelkaart mee. De ingestelde volgdiepte wordt begrensd op
`0` tot en met `10`; standaard is `0`.
- Diepte `0` exporteert de wortelkaart en haar pagina-afhankelijkheden.
- Een gevolgde `cmapSlug` van een geplaatst concept verhoogt de diepte met één.
- De bron van een `derivedView` wordt op dezelfde diepte geladen, ook als
de dieptelimiet al is bereikt.
- Iedere CMap-slug wordt hoogstens één keer bezocht, zodat cycli niet tot
eindeloze recursie leiden. De huidige implementatie volgt diepte-eerst;
de eerste bezochte route bepaalt de verdere verwerking van een kaart.
- De exporter verzamelt `pageSlug` en `descriptionPageSlug` van geplaatste
concepten, plus `metadata.explanationPageSlug` van meegenomen kaarten.
- Gewone links in de Markdown van die pagina's worden niet recursief gevolgd.
- Herkende lokale `/uploads/`-verwijzingen in Markdown en `src`/`href`-attributen
worden als bijlagen ingebed, eenmaal per URL per pagina. Externe bestanden
en `imageSource` worden niet zelfstandig als bijlage opgehaald.
Bij gelijke concept-id's verzamelt de bundelbouwer de eerst aangetroffen
conceptinhoud. In normale exports komt die inhoud uit het gedeelde conceptmodel.
De resulterende bundel wordt vóór teruggave met `validateBundle` gecontroleerd.
## 11. Importgedrag en limieten
De browser leest en valideert het geselecteerde bestand vóór de importwrites.
De maximale bestandsgrootte is **256 MiB** (`268435456` bytes), inclusief
JSON en Base64. Een afzonderlijke gedecodeerde bijlage mag maximaal
**50 MiB** (`52428800` bytes) bevatten.
De server begrenst daarnaast ieder geserialiseerd CMap-opslagdocument op
**10 MiB** (`10485760` bytes). De bestandsgroottegrens van de bundel vervangt
deze afzonderlijke opslaggrens niet.
Conflicten worden bepaald op `pages[].reference` en `cmaps[].slug` ten
opzichte van de geladen catalogi. De gebruiker kiest voor vervangen of
overslaan van bestaande records. Nieuwe records worden in beide gevallen
geïmporteerd. Overslaan van een pagina slaat ook haar bijlagen over.
De importer verwerkt eerst pagina's en daarna CMaps, telkens in arrayvolgorde.
Bij een nieuwe pagina met bijlagen wordt eerst de pagina aangemaakt, daarna
worden bestanden geüpload en wordt de Markdown met nieuwe URL's opgeslagen.
Bij een bestaande pagina worden de bestanden vóór de pagina-update geüpload.
Pagina-updates gebruiken `baseVersion`; CMap-updates laden de bestaande kaart
en slaan via de repository op.
Voor iedere CMap voegt de importer de gedeelde conceptinhoud weer samen met
de lokale plaatsingen en bouwt een `CmapModel`. De server normaliseert
conceptidentiteiten. Bij gelijke namen na trimmen en omzetting naar kleine
letters kan een bestaande conceptidentiteit worden gebruikt. Als daarbij de
id verandert naar die van een bestaand concept, kan de bestaande inhoud
worden behouden. Bij updates van gedeelde conceptinhoud kunnen ook andere
kaarten met datzelfde concept de gewijzigde inhoud tonen.
De overdracht is geen enkele database-transactie voor de gehele bundel.
Een fout tijdens upload, modelopbouw of opslag kan optreden nadat eerdere
records al zijn geïmporteerd. De grens van 50 MiB wordt pas bij het decoderen
voor upload gecontroleerd. Geldige bundelvalidatie garandeert daarom niet dat
alle vervolgstappen slagen. Na succesvolle import worden de catalogi vernieuwd
en wordt `rootCmapSlug` geopend. Versiegeschiedenis uit de bron wordt niet
hersteld; de doelwiki legt normale nieuwe versies vast.
## 12. Bekende verschillen tussen schema en uitvoering
Deze punten beschrijven het huidige gedrag, geen uitbreiding van het schema:
| Onderwerp | JSON Schema | Huidige uitvoering |
| --- | --- | --- |
| Validatiemethode | Volledige structurele veldregels. | De importer roept `validateBundle` aan en laadt het schema niet. |
| Onbekende velden | Op meerdere objecttypen verboden. | De browservalidator wijst onbekende velden niet algemeen af. Het model kan ze vervolgens weglaten. |
| Exportmetadata | Typen voor `$schema`, `exportedAt` en `generator`; `date-time` voor het tijdstip. | Deze velden worden niet door `validateBundle` getoetst. |
| Documentarrays | `concepts`, `items` en `connectors` verplicht. | Ontbrekende of niet-array waarden worden in meerdere validatorlussen als lege arrays behandeld. |
| Getallen | JSON-getallen vereist. | De validator gebruikt bij item-id's, posities en connector-eindpunten `Number(...)`, waardoor sommige andere JSON-typen de controle kunnen passeren. |
| Verbindingszin | Indien aanwezig moet `conceptId` een geldige concept-id zijn. | Het huidige model exporteert bij `phrase` expliciet `conceptId: null`; de browser accepteert dit, het schema niet. |
| Lettergroottes | `fontSize` en `synopsisFontSize` op items zijn `number`. | De Modelgids-export bevat `"11pt"`, `"9pt"` en `"0.84em"`; model en browservalidator accepteren die strings. |
| Submapkleuren | `submapBackgroundColor` en `submapBorderColor` zijn `string`. | De Modelgids-export bevat expliciet `null`; model en browservalidator accepteren dit. |
| Connector-id | Optioneel. | `CmapModel` vereist een integer-id en wijst dubbele connector-id's af, pas bij de modelopbouw tijdens import. |
| Optionele velden | Onder meer typen voor `kind`, `label`, opmaak en conceptinhoud. | Niet al deze typen en verplichte conditionele velden worden door `validateBundle` gecontroleerd. |
| Submapstructuur | `parentSubmapId` heeft een type; `derivedView` heeft veldregels. | De bundelvalidator controleert geen submapcycli, ontbrekende ouders of het bestaan van de afgeleide bron/wortel. |
| Behoud van velden | Open uitbreiding van document, items en connectoren; onder meer `viewport` en `hidden` toegestaan. | Het huidige model serialiseert alleen ondersteunde velden; `viewport` en itemveld `hidden` worden niet bewaard. |
De Modelgids-export voldoet dus niet volledig aan het gepubliceerde
JSON Schema, hoewel de huidige bundelvalidator hem accepteert. Het schema
zou onder meer de lettergroottes, nullable submapkleuren en de bestaande
phrase-representatie moeten afstemmen op de uitvoering. Dat staat los van
de verdere domeinwijziging om verbindingszinnen als gedeelde concepten te
modelleren (§ 1.2).
Voor een nieuwe externe generator: voldoe aan het schema én § 9, lever unieke
connector-id's, laat `conceptId` bij verbindingszinnen weg en gebruik de
gedocumenteerde veldtypen. Reken niet op een byte-identieke export na import.
## 13. Voorbeelden
### 13.1 Analyse van de aangeleverde Modelgids-export
Bronbestand: [modelgids-lts-opzet-cmap.json](wiki-data/modelgids-lts-opzet-cmap.json).
De export vermeldt generator `Racket Wiki 0.2.122` en tijdstip
`2026-09-18T13:35:14.928Z`.
| Onderdeel | Aantal / waarde |
| --- | --- |
| CMaps | 1: `modelgids-lts-opzet` |
| Gedeelde concepten | 15: het hoofdconcept en 14 hoofdstukconcepten |
| Diagramitems | 16: 15 conceptplaatsingen en 1 phrase-item |
| Phrase | Item `2`, label `omvat`, `conceptId: null` |
| Connectoren | 15: één lijnstuk van item `1` naar phrase `2`, veertien van phrase `2` naar items `3` t/m `16` |
| Wikipagina's | 15: veertien conceptuitlegpagina's en de CMap-uitlegpagina |
| Bijlagen | 12, alle met MIME-type `image/png` |
| `conceptRelations`, `conceptOwnerships` | Beide afwezig |
| `conceptMaps` | Lege array |
| Ontbrekende afhankelijkheden | `missing.cmaps` en `missing.pages` beide leeg |
Onderstaand fragment selecteert de eerste drie items en de eerste twee
connectoren. Alleen de genoemde velden zijn overgenomen; dit is geen
zelfstandige importbundel:
```json
{
"items": [
{
"id": 1,
"conceptId": "fdaf8df6-57b5-4eaf-98af-d2fcc00c1439",
"kind": "concept"
},
{
"id": 2,
"conceptId": null,
"kind": "phrase",
"label": "omvat"
},
{
"id": 3,
"conceptId": "7aa9ca75-a272-4a74-aca2-f2b586849bfa",
"kind": "concept"
}
],
"connectors": [
{ "id": 1, "sourceId": 1, "targetId": 2, "hasArrow": false },
{ "id": 2, "sourceId": 2, "targetId": 3, "hasArrow": true }
]
}
```
Door de concept-id's op te zoeken in `concepts` volgt de uitspraak:
```text
Modelgids LTS Opzet — omvat → 1. Inleiding, doel en scope
```
De dertien andere uitgaande connectoren drukken dezelfde relatie uit naar
de overige hoofdstukken. Daarmee verbeeldt één phrase-item veertien
uitspraken. De tekst `omvat` heeft in deze export geen eigen `ConceptId`.
Ook heeft geen van deze uitspraken een afzonderlijk geëxporteerd
`ConceptRelation`-record. Hun betekenis is hier af te leiden uit de
diagramstructuur, niet uit een expliciet opgeslagen semantische relatietabel.
In het bedoelde domeinmodel zou het verbindingsconcept `omvat` een eigen
gedeelde identiteit hebben, en zouden de uitspraken bronconcept,
verbindingsconcept en doelconcept verbinden. De plaatsing met item-id `2`
zou dan alleen een weergave van dat verbindingsconcept zijn. Dit is de
gewenste interpretatie; bovenstaande JSON toont de huidige representatie.
### 13.2 Aanvullend voorbeeld: RWS Dingen
Bronbestand: [rws-dingen-cmap.json](wiki-data/rws-dingen-cmap.json).
Deze bundel van `2026-09-18T13:41:32.898Z` bevat 5 CMaps, 161 gedeelde
concepten, 77 pagina's en 14 bijlagen. `missing.pages` bevat 87 verwijzingen;
`missing.cmaps` is leeg. Dat betekent dat de betreffende pagina-inhoud niet
is meegeleverd, niet dat die verwijzingen uit de concepten verdwijnen.
De export vermeldt niet waarom het ophalen per pagina is mislukt.
| CMap | Items | Phrases | Connectoren |
| --- | ---: | ---: | ---: |
| `rws-dingen` | 38 | 9 | 41 |
| `model-lts` | 45 | 10 | 42 |
| `rws-lbs-architectuur` | 69 | 21 | 71 |
| `review-zuidasdok-augustus-2026` | 59 | 8 | 63 |
| `onderhoudsgui-lts` | 6 | 3 | 6 |
Alle 51 phrases hebben `conceptId: null`. Een phrase-label hoeft
grammaticaal geen werkwoord te zijn: deze export bevat bijvoorbeeld ook
`Architectuur`, `Personen` en `Introductie`. `label` is vrije tekst.
De 166 niet-phrase-plaatsingen verwijzen naar 161 gedeelde concepten.
Het concept `RWS Model LTS` staat bijvoorbeeld tweemaal op `rws-dingen`,
met item-id's `2` en `33` maar dezelfde concept-id. `Architectuurprincipes`
komt met dezelfde concept-id voor op twee verschillende kaarten. Dat
illustreert waarom `ItemId` en `ConceptId` verschillende typen en
verschillende identiteitsbereiken hebben.
Onderstaand fragment is de daadwerkelijke `mapReference` van item `23`
(`kind: "submap"`) op `rws-dingen`; het is geen zelfstandige bundel:
```json
{
"id": "cmap-23",
"itemIds": [6, 7, 8, 9, 15, 20, 21, 22, 24, 29, 31, 32, 33, 34, 35, 40, 41, 42, 43],
"rootItemId": 23,
"title": "RWS TODO"
}
```
Dit item heeft `separateMap: true`, `childMap: "RWS TODO"` en layouts
voor zowel `root` als `cmap-23`. De referentie beschrijft een interne
weergave van de kaart; er hoeft geen afzonderlijk `cmaps[]`-object met
slug `cmap-23` te bestaan.
### 13.3 Aanvullend voorbeeld: RWS LBS Architectuur
Bronbestand: [rws-lbs-architectuur-cmap.json](wiki-data/rws-lbs-architectuur-cmap.json).
Deze bundel van `2026-09-18T13:44:02.082Z` bevat 8 CMaps, 96 gedeelde
concepten, 40 pagina's en 23 bijlagen. `missing.pages` bevat 55 verwijzingen;
`missing.cmaps` is leeg. De drie kaarten met eigen diagramitems zijn
`rws-lbs-architectuur`, `model-lts` en `modelgids-lts-opzet`.
De overige vijf kaarten gebruiken `derivedView`:
| Afgeleide kaart | `sourceCmapSlug` | `rootItemId` |
| --- | --- | ---: |
| `kaderdocumenten-beoordelen` | `rws-lbs-architectuur` | 36 |
| `sd-inhoud` | `rws-lbs-architectuur` | 48 |
| `metamodel-lbs` | `rws-lbs-architectuur` | 89 |
| `refactoring-lts-model` | `model-lts` | 26 |
| `lts-aspecten-voor-modelverniewing-en-versimpeling` | `model-lts` | 37 |
Bijvoorbeeld: `kaderdocumenten-beoordelen` bevat geen eigen items of
connectoren, maar verwijst met dit daadwerkelijke `derivedView`-object
naar de bronkaart:
```json
{
"rootItemId": 36,
"sourceCmapSlug": "rws-lbs-architectuur"
}
```
`rootItemId` moet hier in de bronkaart worden opgezocht. `sourceCmapSlug`
is een opgeslagen CMap-adres, terwijl `cmap-36` in een `MapReference` een
interne referentie- en contextkey is. Deze waarden zijn niet uitwisselbaar.
Dat ook `model-lts` in deze bundel staat, past bij de exportregel die de
bronkaart van een afgeleide weergave meeneemt.
De twee bronkaarten `rws-lbs-architectuur` en `model-lts` zijn inhoudelijk
gelijk aan hun records in de RWS Dingen-export. Deze voorbeelden zijn
daarmee overlappende bundels, geen afzonderlijke definities van dezelfde
concepten. Ook deze derde bundel bevat phrases zonder gedeelde concept-id
en geen afzonderlijk geëxporteerde semantische relaties.
### 13.4 Volledige kleine versie-1-bundel
Deze bundel toont twee gedeelde concepten, een verbindingszin, twee connectoren,
een pagina en een ingebed tekstbestand. `SGFsbG8K` is Base64 voor `Hallo`
gevolgd door een newline. De tijdelijke concept-id's worden bij opslag opgelost.
De verbindingszin gebruikt de huidige versie-1-representatie. Het voorbeeld
is dus geen implementatie van het gewenste verbindingsconcept uit § 1.2.
```json
{
"$schema": "/schemas/racket-wiki-cmap-bundle-v1.schema.json",
"format": "racket-wiki-cmap-bundle",
"formatVersion": 1,
"exportedAt": "2026-09-18T12:00:00.000Z",
"generator": "Voorbeeldgenerator",
"rootCmapSlug": "voorbeeld",
"cmaps": [
{
"slug": "voorbeeld",
"title": "Voorbeeldkaart",
"document": {
"schemaVersion": 2,
"metadata": {
"namespace": "",
"tags": ["documentatie"],
"summary": "Een eenvoudige voorbeeldkaart",
"explanationPageSlug": ""
},
"concepts": [
{ "id": "new:proces" },
{ "id": "new:documentatie" }
],
"items": [
{
"id": 1,
"conceptId": "new:proces",
"kind": "concept",
"x": 100,
"y": 80,
"width": 200,
"height": 90,
"parentSubmapId": null
},
{
"id": 2,
"kind": "phrase",
"label": "heeft documentatie",
"x": 360,
"y": 100
},
{
"id": 3,
"conceptId": "new:documentatie",
"kind": "concept",
"x": 600,
"y": 80
}
],
"connectors": [
{ "id": 1, "sourceId": 1, "targetId": 2, "hasArrow": false },
{ "id": 2, "sourceId": 2, "targetId": 3, "hasArrow": true }
]
}
}
],
"concepts": [
{
"id": "new:proces",
"label": "Proces",
"synopsis": "Een reeks samenhangende activiteiten",
"aspects": ["ontwerp"],
"tags": [{ "type": "person", "value": "Hans" }],
"pageSlug": "proces:beschrijving",
"externalUrl": "https://example.com/proces"
},
{
"id": "new:documentatie",
"label": "Documentatie"
}
],
"pages": [
{
"reference": "proces:beschrijving",
"title": "Procesbeschrijving",
"markdown": "# Proces\n\n[Voorbeeldbestand](/uploads/proces:beschrijving/hallo.txt)",
"tags": ["voorbeeld"],
"attachments": [
{
"url": "/uploads/proces:beschrijving/hallo.txt",
"name": "hallo.txt",
"mimeType": "text/plain",
"contentBase64": "SGFsbG8K"
}
]
}
],
"missing": { "cmaps": [], "pages": [] }
}
```
## 14. Bronnen in de repository
- [JSON Schema](static/schemas/racket-wiki-cmap-bundle-v1.schema.json): veldstructuur en typen.
- [Bundelopbouw en validatie](static/js/cmap/model/interchange.js): selectie, verwijzingen en integriteitsregels.
- [JSON-exporter](static/js/cmap/model/json-exporter.js): modeloverdracht en Base64-export.
- [JSON-importer](static/js/cmap/model/json-importer.js): bestandslimieten, uploads en opslagvolgorde.
- [Overdrachtscontroller](static/js/wiki/cmap/cmap-transfer-controller.js): conflictkeuze en navigatie.
- [Diagrammodel](static/js/cmap/model/concept-map.js) en [conceptmodel](static/js/cmap/model/concept-repository.js): reconstructie en normalisatie.
- [Relatiecontroller](static/js/cmap/controller/cmap-relation-controller.js): aanmaken van phrases en connectoren.
- [Layoutcontroller](static/js/cmap/controller/cmap-layout-controller.js) en [submapcontroller](static/js/cmap/controller/cmap-submap-controller.js): contextlayouts en interne kaartreferenties.
- [Opmaakmodel](static/js/cmap/model/appearance.js): interpretatie van lettergroottes en opmaakwaarden.
- [Aangeleverde Modelgids-export](wiki-data/modelgids-lts-opzet-cmap.json): concrete veldwaarden en het uitgewerkte voorbeeld in § 13.1.
- [Aangeleverde RWS Dingen-export](wiki-data/rws-dingen-cmap.json): gedeelde plaatsingen, interne submaps en tags (§ 13.2).
- [Aangeleverde RWS LBS Architectuur-export](wiki-data/rws-lbs-architectuur-cmap.json): afgeleide kaarten en bronafhankelijkheden (§ 13.3).
- [CMap-opslag](private/cmap-storage.rkt): servernormalisatie van conceptidentiteiten.
- [Databasemigraties](private/migrations.rkt): scheiding van gedeelde conceptinhoud en kaartitems, met expliciet behoud van phrase-tekst.
- [Bundeltests](test/cmap-interchange.test.js) en [import/exporttests](test/cmap-json-import-export.test.mjs): bestaande gedragscontroles.