From 53c8569973f951e4c0a2ae43443e9ff855e47458 Mon Sep 17 00:00:00 2001 From: Hans Dijkema Date: Fri, 18 Sep 2026 15:52:01 +0200 Subject: [PATCH] json formaat beschreven --- JSON-FORMAAT.md | 940 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 3 + 2 files changed, 943 insertions(+) create mode 100644 JSON-FORMAAT.md diff --git a/JSON-FORMAAT.md b/JSON-FORMAAT.md new file mode 100644 index 0000000..7a08fe3 --- /dev/null +++ b/JSON-FORMAAT.md @@ -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` 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. diff --git a/README.md b/README.md index 7608ff4..ce34a48 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,9 @@ and their editor remain unchanged. A small self-hosted wiki with a Racket backend and an HTML5/CSS/JavaScript frontend. +De formele Nederlandse beschrijving van het JSON-export- en importformaat staat in +[JSON-FORMAAT.md](JSON-FORMAAT.md), met velddefinities, validatieregels en een volledig voorbeeld. + Version 0.2.9 uses PostgreSQL as the wiki's complete content store. Users, sessions, pages, immutable page versions, attachment metadata and attachment bytes live in PostgreSQL. PostgreSQL full-text search is built into the page table and exposed by the wiki search UI. The first-run flow is web based. An unconfigured wiki redirects immediately to `/setup`. Setup asks for the PostgreSQL connection, creates the schema, downloads the pinned frontend libraries and creates the first administrator. After setup the browser is sent to `/login`.