# 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` 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-`. | | `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` | 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-`. 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.