@concordance-wiki/site
v1.0.0
Published
Site generation: slots, default theme, islands, stylesheet, markdown rendering, page budget and the site build.
Readme
Why
Concordance publishes a wiki where every word of your business has a page, as a static folder you drop on GitHub Pages, GitLab Pages or a bucket; it works over file:// too. You install @concordance-wiki/concordance for that. This package is the part of it that writes the site: the slots of a page and their view models, the default theme with its islands and stylesheet, the markdown renderer, the search index, the page budget and the accessibility checks every page passes before it is written. Install it alone to build on the engine, or to write a theme or a component as a plugin.
Quick start
npm install @concordance-wiki/siteA note rendered the way an entity page shows it, then checked against the accessibility rules the build applies to every page:
import { checkAccessibility, renderMarkdown } from "@concordance-wiki/site";
const note = "# Occurrence scan\n\nThe scan reads every note.\n\n## Findings\n\nOne `E-LINK-BROKEN` per missing target.\n";
const rendered = renderMarkdown(note);
rendered.title; // "Occurrence scan"
rendered.sections.map((section) => section.heading); // [undefined, "Findings"]: the lead, then one section per H2
checkAccessibility("<html><body><main><h1>Occurrence scan</h1></main></body></html>");
// [{ rule: "html-lang", message: "the html element carries no lang attribute" }, { rule: "landmarks", ... }]A theme plugin overrides a slot with its own component and ships its tokens; the theming guide shows the whole contribution.
What you get
- Every page of the wiki: the home, one page per entity and per keyword, the A–Z index, the spaces, the to-do list, the search results, the about page and the page served for a missing address, written by
buildSitefrom a model, every href relative. - Slots, not templates:
SLOT_NAMES,renderSlot,resolveTheme; a theme or a type module overrides one slot and keeps the rest. - A theme from six colours:
loadTheme,paletteColours,tokensStylesheet, the palette, light and dark, derived fromtheme.yaml. - Islands, hydrated on demand: the mentions panel, the table of contents, the pinned pages, the side panels, the document and contract viewers, one hashed bundle each.
- Notes as sanitised HTML:
renderMarkdownmarks the written links and the recognised words apart, so a reader always knows which is which. - Search without a server: a sharded index tokenised with the language packs, one shard loaded per word typed, with facets by type, repository and domain.
- A budget and a bar:
measureBudgetmeasures every page against the 150 kB budget,checkAccessibilityandcheckContrastreport the one that fails the structural, ARIA and contrast rules. - A gallery for theme authors:
buildGallery, every slot in every state and every type from its template, whatconcordance galleryrenders.
Documentation
- Theming
- Accessibility
- Command line, the
build,renderandgallerycommands - Home page, the demo wiki and the changelog
Part of Concordance, MIT licence.
Slots and themes
SLOT_NAMES and SlotProps name the sixteen slots and type what each receives. defaultComponents implements every slot in Preact, rendered to static HTML by renderPage (a complete document) and renderSlot (one slot). useSlot lets a page compose the panels of the current theme. resolveTheme starts from the default theme and applies the components of every theme contribution of the plugin registry, the last one winning, and loads the tokens of the last theme when it can locate the package, with importThemeModule loading a component by its path relative to the plugin package and packageDirectoryOf finding that package. The same resolution files the components named for one type, one attribute or one mapped section (EntityPage@<type>, Attribute@<name>, Section@<key>, read by parseComponentName) from the themes and from the components/ of the type modules it is given (importFile loading a module file by its absolute path), the themes winning, under ResolvedTheme.typed, which pageComponentFor, attributeComponentFor and sectionComponentFor answer and the default entity page reads through useAttributePart and useSectionPart.
Theme files and the stylesheet
loadTheme reads and validates a theme.yaml, resolving its logo, favicon, stylesheet and assets against the file, chromeOf turns it into the title, logo, favicon, stylesheets and footer of every page and writeThemeAssets writes its files under assets/. tokensStylesheet turns the theme into custom properties, the nine colours of a palette derived by paletteColours from the six a theme must give, every palette block naming its ColourScheme in --scheme and setting --shadow-float, the shadow of what floats over the page, none in the dark scheme, fontFacesStylesheet binds the families the default theme ships (TEXT_FAMILY, Instrument Sans, and MONO_FAMILY, IBM Plex Mono, FONT_FACES naming their eight woff2 files under FONTS_DIRECTORY with FONTS_LICENCE, copied by writeThemeAssets and read by fontFiles), siteStylesheet assembles the faces and the tokens, base, components layers (the last two read from the files CSS_FILES lists under assets/css/, one per concept, in the order of the list) and projectStylesheet wraps the project's file in the project layer, declared last.
Islands
island wraps an interactive component so that its props travel in the page, mountIslands hydrates them in the browser and bundleIslands builds one hashed, minified classic script per island with esbuild, byte-identical from one build to the next, so that a page opened from the disk runs every island in every browser (only a bundle imported on demand, IslandEntry.module, is a module), defaultIslands naming the eleven islands of the default theme and mergeIslands adding the UI components the theme resolution collected from the registry (ResolvedTheme.islands). MODE_SCRIPT and PANELS_SCRIPT are the two inline scripts of a page, applying before the first paint the colour scheme a reader chose with the mode-switch island and the side panels they folded with the panels island (PANELS_ISLAND, Panels, PanelHandle, SidePanel, wired by wirePanels: a handle on the edge of the tree of the space and of the right panel folds it to a 44 px strip, [ and ] do the same from the keyboard, the folded panels living in localStorage under PANELS_STORAGE_KEY and as data-panels on the root). The pins island (PINS_ISLAND, Pins, PinButton, wired by wirePins) draws under the header the pages the reader pinned from the button after the title of a page, as a row of chips the reader unpins by hand, the ones beyond the room of the bar behind a summary opening the full list (fitCount, filterPins), kept in localStorage under PINS_STORAGE_KEY (parsePins, pinsValue, pinPage, unpinPage) and written from one markup function (pinsMarkup) the gallery previews with.
The chrome
The top bar of every page carries the mark and the name of the site, the search field with its / shortcut, the links to the spaces page (SPACES_PAGE), the alphabetical index and the recent changes (HOME_RECENT_ANCHOR) and the mode switch (a toggle named "Dark mode", pressed while the dark scheme is displayed, drawing the glyph of the scheme it switches to, switchGlyph; its island reads the scheme in force from --scheme on the root, displayedScheme, and stores the other one, or nothing when the system already gives it, toggleChoice). The footer (footerOf) is a card in two columns then a line: what the tool knows, the build instant, the repositories counted and linked to the spaces page, the link to the about page (ABOUT_PAGE), the generator and its licence, the tool named only when the theme credits it (TOOL_NAME, REPOSITORY_URL); what the organisation declared, the legal pages of project.legal or of a note at legal/<page>.md (legalLinksOf, LEGAL_PAGES, the accessibility link worded with its declared state by accessibilityStatusLabel) then the text and links of the theme, the column shown only when something was declared; and the build line with the profile (profileLabelOf), the notes counted (pageCountOf) and the to-do link with its count, every string worded by footerLabels from the catalogue. The header folds the search field, the spaces with their note counts (spaceCountsOf, spaceLinksOf), the tree of the space of the page and the links into a <details> drawer behind a menu button, kept in view in the bar where it has room, the related pages beyond RELATED_CONDENSED in a disclosure of their own, closed where the panel is condensed beside the text, and the change date worded in short too for the narrow line. The search island draws under every field, the one of the header, the one of the drawer and the one of the home page alike, the live results as SearchSuggestions rows (suggestionOf, the query marked in the title by markTitle, the detail line worded by suggestionDetail from the type and the summary of a note, the citations of a note of a glossary source named in meta.glossary, or the documents of a keyword page, the namesakes told apart by disambiguated, the link to the whole list by seeResultsHref), counts the matches next to the field of the home page and walks the rows with the arrow keys (wireArrows). The header form carries the CLEAR_CLASS button the island shows while the field holds a query.
The entity page
The entity page view model (entityPageOf) carries the declaration of the type (declarationOf), the tree of the space of the page (spaceOf: its source with its initials, initialsOf, and the link to its page, the folders on the way open, every folder linked to its list, the current page marked, at most SPACE_PAGES_MAX pages of a folder listed around it) and its breadcrumb (breadcrumbOf, the space linked to its page, every folder to its list), when the note last changed relative to the build (changedOf), the declared attributes labelled by the profile (panelOf), the attributes the profile does not declare kept as written (othersOf), the sections of the note marked with the mapped section they match (sectionsOf) and the labels of the page in the site language (entityPageLabels). The default entity page lays them out as the tree on the left, the breadcrumb, the title, the line naming the type, the change and the space, the note with the legend of its three marks (a written link, a recognised word with a note, a recognised expression without one) and the path of the file with its edit link in the centre, and on the right the blocks of the panel, the properties, the table of contents, the related pages and the neighbourhood folded behind its line, every block a closed <details> the stylesheet unfolds where the layout has room.
The documents of a page
The page of an entity with documents (EntityPageProps.documents, read by documentsOf from the documents of its fragment) offers each file for download, its PDF, a rail of positions and the extracted text of every position in disclosure blocks anchored L<n>. The document-viewer island (DOCUMENT_VIEWER_ISLAND, plain JavaScript) imports the viewer-pdf bundle on demand and opens the PDF with pdf.js (createViewer: the pages reached through the rail, zoom steps shown as a percentage, a find box over the extracted text), the worker travelling as the viewer-pdf-worker bundle. viewerIslands() names both and buildSite bundles them only when a fragment carries a preview (needsViewer), so that no page ever loads them on its own.
The neighbourhood
layoutNeighbourhood places the centre and the nodes of the neighbourhood map on a ring with labels that never overlap on its width estimate, in integer coordinates, and the default theme draws them with a glyph shape per type and a dashed square per noteless word, the total the model holds worded under the map when it exceeds the nodes drawn, the map hidden from assistive technologies and described by the list of neighbours that follows it, each entry naming the entity, its type and the nature of the link as the build labels them from the profile, the relation read from the page. The neighbourhood fold of the entity page, once open, becomes the map as the panel shows it (NeighbourhoodFold and mapOpen): the summary turns into a back control with the title of the map and the name of the page, the other blocks of the panel go from the desktop width, and the Neighbourhood slot draws the distance the model records, a type filter of checkboxes that works from the stylesheet alone, the framed map with its legend, the total when the model holds more, the list of neighbours with their types and counts and the note on why the map stops at six, its strings in the site language through neighbourhoodLabels and NeighbourhoodLabels.
The related pages
relatedMentionsOf and mentionsPanelOf build the mentions of a page: for a note of the model, the located provenances of its links, read in the citing note (mentionsOf); for a keyword page, a meeting or a document page, which nothing links to, the pages its own passages evoke (evokedMentionsOf, relatedViewOf), from the passages of the keyword fragment or the provenances read in its files; each with the title and type of the related page, the passage the scan kept or the sentence around a written link and the words naming the entity (surface), served page by page, most passages first, the first passage of each of the six pages listed first, the number of related pages and the labels of the block (relatedLabels). buildSite writes fragments/<id>.mentions.json for every entity with a related page, one file per entity and never a global index, the page carrying the first DEFAULT_MENTIONS_INLINE (20, build.mentions_inline) and, under MENTIONS_EMBEDDED_MAX (200) mentions in all, the rest in a JSON script block that the mentions-panel island (MENTIONS_ISLAND) reads before it fetches the fragment. The default panel shows them as the related pages, one entry per page ordered by number of passages (groupByPage), with its type, its count and an excerpt marked "cited" when the page writes a link, six entries (RELATED_INLINE) before the button naming the others, three (RELATED_CONDENSED) where the panel is condensed and two (RELATED_PHONE) on a phone, a text filter and a type filter (typeCounts) once the island runs.
The keyword page
with the word at its place, spaceWithPageOf, the passages by page in corpus order, the files of one page in one group (locatedGroupsOf), with the type, the title and the count of each page and where each passage stands, PASSAGES_IN_VIEW (2) passages of a page in view and the others folded under their count (foldedGroupOf), PASSAGE_GROUPS_IN_VIEW (6) pages in view and the other files behind a disclosure (passageBlocksOf), the similar expressions of the fragment with the occurrences of a keyword page among them, a <meta http-equiv="refresh"> page counted in the summary). The keyword page is the entity page without the note (keywordPageOf: the tree of the space the word is filed in, the glossary when there is one, else the space of its first passage, with the word in the folder of its first passage of that space at its alphabetical place with its passage count, keywordSpaceOf and spaceWithPageOf, and the breadcrumb "space › Terms › word", since when the word is used from the oldest git date among the citing files, usedSinceOf, the notice from the catalogue with the lead to propose a definition on the forge of the glossary, the three counts and the names of the spaces, the passages by file in corpus order with the type and the title of each file and where each passage stands, passageLocationOf reading the timecode, the page or the slide from the fragment of the citing page, the expression marked, the neighbourhood drawn from the neighbours block since a word nobody defined carries no link, keywordNeighbourhoodOf, six nodes at most, the similar expressions of the fragment, one per page with the other forms it was met under and how many passages use it, similarOf, the neighbourhood and the mentions with the note that none is cited, and the labels of the page in the site language, keywordPageLabels) and a former keyword address forwards to the note that defines the expression since (redirectsOf, a <meta http-equiv="refresh"> page counted in the summary).
The home page
The home page the build writes (homeOf) opens on the question and the search field drawn large, offers the HOME_SHORTCUTS most cited pages as shortcuts, one per title (shortcutsOf), lists the spaces most cited first (spacesOf: a source with its initials, its count in pages or in documents, the date of its newest change worded relative to the build, and the href of its page), HOME_SPACES_SHOWN of them in view and the rest folded, the HOME_RECENT latest changes with their space (recentOf) and one alert per source dormant past staleness.warn_after_days, 180 days without the key (alertsOf, warnAfterDays), the strings of the page coming from the catalogue (homeLabels) and those of the live results travelling with the field (suggestionLabels).
The A–Z index
The A–Z index (planIndex, indexOf) lists every entity and keyword page in the collation of the project locale with, per entry, its letter, the label of its type, its first line (summaryOf: the summary of the note cut at a word by excerptOf, or for a word without a definition the passage of the file that uses it most, mostCitedPassageOf) and the pages citing it (citingPages), the counts of the words and the notes, the filters leading to the results page by type, by space or to the words without a definition (filtersOf) and the labels of the page in the site language (indexLabels), the default theme laying every letter out as a table under its heading. The alphabetical index sorts the titles with the collate function the caller passes (languagePack(locale).compare in the command line, the order of the shipped packs otherwise, computed without the collation data of the runtime) and, past INDEX_SEGMENT_BYTES for the whole index as one page, writes one page per letter with entries.
The search index and the results page
buildSearchIndex turns a model and its fragments into the search index with the tokeniser it is given (FIELD_WEIGHTS: title 5, aliases 4, summary 2, body 1, type, application, domain, status and source 1; the body cut at bodyMaxChars, DEFAULT_BODY_MAX_CHARS being 20 000), searchIndexFiles writes it as search/meta.js and one search/<prefix>.js per shard, classic scripts calling window.__concordanceSearch.shard(name, data) so that they load from a file:// page, and normalizeQuery, queryWords, shardOf, shardFile, rank and plural are the pieces the search island shares with the build, which also writes in the table the counts of every facet value (countFacets), the names of the sources and the strings of the results page in the site language (searchLabels, plurals frozen by category with pluralForms). The island (mountSearch, searchRunner, shardLoader, wireShortcuts) loads the table on focus and one shard per word typed, draws the best results under the header field and the whole list on the results page, and gives the field the / and Escape shortcuts. On the results page it reads the state of the address (parseSearchState, searchQueryString: the query and the selected values of the FACET_NAMES type, source, domain and application), filters the hits in the browser (filterEntries), counts every facet under the filters of the others (countFacets), and builds the view (resultsPropsOf, facetsOf, activeFiltersOf) with a value at 0 disabled rather than hidden and the selected values recalled above the results, each link followed in place and pushed to the history (SearchLocation), the address rewritten once the reader pauses typing (REPLACE_DELAY), replayed on popstate with the scroll position remembered per address (ScrollMemory, scrollMemory over a web storage storageOf reaches, SCROLL_KEY); the rows are drawn RESULTS_BATCH at a time, the button under them, worded from showNext, adding the next batch in place (more of the props), and any change of state starts again from the first batch. A keyword page travels in the table with keyword: true and its occurrences and documents, is drawn as a result-keyword row with the notice that no note defines it and its counts (keywordDetail), is kept, isolated or excluded by the NOTELESS_FACET (nonote=only|exclude, NOTELESS_FILTERS, toggleNoteless, notelessFacetOf, notelessCount), and never comes before an entity of the same score (rank takes a keyword predicate for its tie-break). The results page lays the facets out in a full-height left column as checkbox groups (facetsOf flags the secondary ones folded and the keyword type keyword, labelled "Without a definition" in the table, PRIMARY_FACETS names the open ones), the active filters as chips, the first result as an expanded card with its type, its title, its citations (citedDetail, and the bare citedCount for the rows after it; neither for a page nothing cites), its whole summary and its facts (factsOf: the space, the aliases and the broader term the entity table carries, SUMMARY_MAX_CHARS bounding the summary through excerptOf), the following results condensed on one line each, a word without a note dashed with keywordDetail; the heading of the page is kept for assistive technology alone. rank puts the most cited first among equals and hitsOf lists the whole table that way. closestForm and closestOf propose the closest form of the dictionary when nothing matched. The empty state (emptyOf, emptySummary, EmptyResults) tells two causes apart: the filters left every match out, and the page offers to lift each of them with the count of what comes back (liftFilter), or no file uses the word, said so with the line on the prefix search; either way the page of the word itself is an exit when the corpus has one.
The spaces pages
The spaces page (spacesPageOf, written at SPACES_PAGE) lists every space in one table, the most cited first (spaceRowsOf: the badge, the name, what the space holds from sources[].description or the labels of its dominant types, contentOf, its page count and its newest change, worded in days once past the staleness threshold), and the page of a space (spacePageOf, at spacePagePath, spaceHref from another page) carries the breadcrumb from the spaces page, the badge, the title, the declared description, the count, the repository (repositoryOf) and the newest change, the categories of the repository (categoriesOf, from topFoldersOf, each linking to its list, or to the page the tree lists first under the folder when a note takes the address of the list), the SPACE_RECENT latest changes with their category (spaceRecentOf, categoryOf) and the SPACE_WORDS pages the notes of the space cite most (spaceWordsOf, citedInSpace), under a bar whose search field says it keeps to the space and carries it as the source facet (SearchField.source, honoured by the island's fieldState), the rows of the home page, the drawer and the breadcrumbs leading to these pages.
The about page
The about page (aboutOf, written at ABOUT_PAGE) says where the site comes from and when it was built: the breadcrumb from the home page, the sentence saying the site is rebuilt at every change of the repositories, the figures of the build (aboutFiguresOf: the build instant, the notes as pages, every entity as an indexed word; no duration, which the model does not record), the sources table (aboutSourcesOf: every repository in the order of the home page, its nature from sources[].title or its dominant types, natureOf, the commit read shortened to SHORT_COMMIT characters, what was kept counted as the home page counts it, its newest change, a dormant source dated in days and named under the table with its own threshold), what the site does not contain below inference.keyword_pages.min_occurrences (DEFAULT_KEYWORD_THRESHOLD without the key) with the link to the to-do page, how a page is corrected with the link to project.contribute_url, a word on pseudonymisation when the configuration enables it, and the sections of the markdown file project.about names, rendered after (renderMarkdown). The About slot renders it; defaultAboutLabels words it in English when the site gives no labels.
The category list
Every folder of a space, at every depth, gets the list of its notes at the folder's address, and a space whose every note is dated gets one per year and per month as well (listedCategoriesOf, categoryDocumentsOf: the type the folder maps to, the tree of the space with the folders on the way open and this one marked, categoryTreeOf over folderTreeOf or datedFolderTreeOf, the lead counting the notes as the type words it and describing the type from the profile, or counting the pages filed under the path for a folder below the top, categoryLead, the rows with the first highlighted attribute of the type, the summary and the number of related pages, rowsOf, the search field asking to search in the category with its space and type as hidden facets, SearchField.filters, honoured by the live results too, categorySearchField), the sort and the attribute filter as links to pre-rendered variants named by variantFile while they stay within CATEGORY_VARIANTS_MAX, else applied in place by the category-list island (CATEGORY_ISLAND, CategoryIsland, orderRows, filterRows, pageCount paging by CATEGORY_PAGE_SIZE), the CategoryList slot drawing the page either way, its breadcrumb leading from the spaces page to the page of the space.
The meeting page
The page of a meeting (meetingOf, carried as EntityPageProps.meeting and laid out by MeetingPage when the view model has it) reads the date of the note (dateOf), draws the tree of a space whose every note is dated by year and month (isDatedSpace, datedSpaceOf) with the breadcrumb naming the month (datedBreadcrumbOf), words the duration from the duration attribute or the last cue of the transcript (durationOf, durationLabel), says that the participants are pseudonyms when privacy.pseudonymize.enabled reaches the site as pseudonymized (participantsOf), links the decisions the model ties to the meeting (decisionsOf), names why the build grouped its files from the duplicates block of the model (groupingOf, groupingReasonsOf), places the callout of every decision under the last cue of the transcript it was recognised in (decisionsOf reads the cue from the provenances located in the transcript) and offers its representations behind the tabs of the theme (Tabs, TabListIsland, the tabs island wired by wireTabs, which follow the tablist pattern, work as anchors without any script and drive the panels once the script runs), the transcript as timestamped lines with the speaker of every cue, the deck as one tab whose viewer opens as soon as the tab is shown.
The API page
ContractViewer is the island of the api page (CONTRACT_VIEWER_ISLAND), served as a link to fragments/<api id>.contract.json (contractFragmentPath), fetching that view as soon as it mounts and rendering its operations and schemas open in the page without any form or other request, ContractSection framing it after the note with the operations table, the meta line dated by the last change of the contract file (last_modified of the record, the import instant for a URL contract) and the download link at the foot of the block (contractFileTarget for the copy of a path contract, isContractUrl telling a URL apart), and defaultThemeManifest declaring it as the UI component contribution of the default theme (DEFAULT_THEME_PLUGIN, defaultUiComponents). The API page (ApiPage, served by TypedEntityPage in the EntityPage slot when the contract of an api was imported) lists the operations under the current interface in the tree (spaceOf hangs them under its node), tables them after the note through ContractSection (exposedOperations reading the exposes links in the order of the names the contract record keeps, unmatchedOperations the notes OPERATION_UNMATCHED reports that name the API, citingPages counting the callers, contractLabels wording the block from the api.* messages), shows the contract block with its format and its import date, cuts the properties to API_KEYS_MAX keys (apiKeys) and lifts the operations to the top of the related pages (leadType of the mentions panel, OPERATION_TYPE), the notes of the operations citing the API through mentionsOf.
The document page
The template follows the lead of a group of twins: a note keeps the template of its type and gets its documents folded under the article, each behind the line documentSummary words (foldedDocuments, DocumentView.summary); a document whose own file is not a note (leadsWithNote), or a note of the document type (DOCUMENT_TYPE), gets the document page. The page of an office document, a deck or a report, is laid out by DocumentPage when the build hands EntityPageProps.document (documentPageOf: the kind of the original from its extension, the page count, the size worded by formatSize, the date read from the file else from the repository, the author, a count or a size the original does not state read from its PDF preview and said so, the files that make the document and the labels of the page, documentPageLabels; centralDocument picking the document the page centres on, none for a page with a transcript). documentsOf reads a converted file and the PDF kept next to it in the sources as one document (previewTwinOf), the office file as the original and the PDF as its preview with what its reader read. The page draws the tree of its space folded by year and month when every page of the space is dated (isDatedSpace, datedSpaceOf, as for a meeting), three views behind the tabs of the theme, the strip of numbered pages driving the viewer served to open as soon as its panel is shown, and the PDF as the browser shows it without JavaScript. A document whose conversion failed, the W-CONV-FAILED finding recorded for its path (conversionFailureOf, DocumentView.previewFailure), shows in place of the rendering the notice naming the fact, what remains, the original to download and the finding behind a disclosure, the cause first and the check identifier after it, then the extracted text when it was read; the panel writes the state of every representation (representationsOf, DocumentPageView.representations).
The grouped files
Every template says the same way which files the build merged into the page (groupingOf in build/grouping.ts, carried as EntityPageProps.grouping, drawn by GroupedFilesBlock at the foot of the properties block): the line counting the files and naming the criterion, the criteria recorded in grouped_by worded through the entity.groupedBy.* messages (criterionOf, the rungs of a contract import left out, an unknown criterion kept as recorded), each file with its kind (kindOf in build/kinds.ts, "Markdown note", "Presentation", "Text document", "PDF", else the extension in capitals), then "Separate these files" leading to project.contribute_url, else to the lock section of the configuration guide (LOCK_GUIDE_URL). The meeting page keeps the clues of the reconciliation under it (groupingNoteOf, MeetingProps.groupingNote).
The notice of a page whose datum is missing
PageNotice is the one banner the site allows (EntityPageProps.notice, the PageNotice and NoticeExit types): the fact in plain words, what remains, the exits and the finding the build recorded behind a disclosure, the cause first and the check identifier after it. The generic entity page draws it under its header for an api whose declared contract could not be read (contractNoticeOf, from the W-CONTRACT-UNREACHABLE finding, the declared address as an exit when it is one); the document page draws it in place of the rendering of a document whose conversion failed.
The page served for a missing address and the notice on the age of the site
buildSite writes NOT_FOUND_PAGE, 404.html, the page GitHub Pages and GitLab Pages serve for any missing address under the site: the chrome of every page, the cause in plain words, what stays reachable and two exits, the search of the documentation and the list of the spaces (notFoundOf, NotFound). Its head names its own address as a <base> (notFoundBase from the path of site.url, sitePathOf; the root of the host without it) so that its relative hrefs resolve from wherever the host serves it. The not-found island (wireNotFound, notFoundPropsOf) reads the address asked for (missingAddress), words the search on its last segment (queryOf) and lists the nearby pages (nearbyOf: by editDistance on the path, NEARBY_LIMIT at most within nearbyThreshold) from the table of the pages the search index writes, loaded through shardLoader. With site.publish_every_days every page carries between the bar and its content the notice on the age of the site (AgeNotice, ageNoticeOf, repositoryHrefOf), served hidden; the age island (wireAgeNotice, noticeDays, ageInDays, isOld) counts the days in the browser, shows it past AGE_THRESHOLD cadences, and closing it remembers the publication under AGE_STORAGE_KEY (rememberClosed, wasClosed).
The decision page
The page of a decision (decisionOf, carried as EntityPageProps.decision and laid out by DecisionPage when the view model has it) words the status of the note from the profile or keeps it as written (statusOf), draws the tree of a space whose every note is dated by year alone (yearSpaceOf) with the breadcrumb naming the year (yearBreadcrumbOf), names the decision the page supersedes and the one that supersedes it from the supersedes links at either end, the superseded_by key of the note standing in (supersessionOf), and the meetings the model ties to the decision with the day of each and the last cue of its transcript that names the decision, anchored as the page of the meeting numbers its documents (sessionsOf, the documents of a meeting handed in by the caller); the labels come from the decision.* messages (decisionLabels), the count of keys from the rows the panel shows (decisionKeyCount). The related pages of a decision take the evoked view, the pages it cites with the pages citing it (relatedViewOf).
The to-do page
The to-do page lists the entities the W-DOC-NOMD findings name and the keyword pages, with their counts, and reads no other finding. The words are listed a hundred at a time: the first hundred in view, the others behind a disclosure worded "Show the N others" (todo.showOthers), served in the HTML without a script (TODO_VISIBLE_TERMS). A third section, folded, lists the suspected noise (todo.noise, todo.noiseNote): the term candidates of the model marked withheld, the expressions at the publication threshold whose confidence stayed under inference.keyword_pages.min_confidence, best score first, each with its occurrences, its files and its reason worded from its penalties and signals in the language of the site (todo.reasonSpread, todo.reasonBurst, todo.reasonMorphology: "in 68% of the files, 1.2 per file, verb or adverb form"), none linked since none has a page, and a call to add them to the project's stopwords (todo.contribute) leading to project.contribute_url when the project declares one (TodoProps.noise, contributeHref, labels; noiseOf, reasonOf, todoLabelsOf).
Markdown and fragments
renderMarkdown turns a note into sanitised HTML sections and its plain text (plainText), the frontmatter and the first heading removed, every link, image and definition target passed through a resolver that rewrites or drops it, a rewritten link marked written (WRITTEN_CLASS) and every recognised word given as { line, text, href?, title?, keyword? } wrapped where its text is found in its unit in a recognised link (RECOGNISED_CLASS), or a recognised-keyword one (RECOGNISED_KEYWORD_CLASS) for an expression that has a keyword page and no note, the title on the anchor and again as hidden text; a page is marked once, on its first occurrence, a written link on the same line or above counting as the first. markLabels words those titles in the site language, "note: " and "N passages, no note", for the build that writes the fragments. serializeFragment and parseFragment write and read the fragments/<id>.json the build leaves next to the model (EntityFragment: the sections and the text of a note, the images it embeds from the sources, the keyword addresses it took over, the documents it offers with the text of their pages, the passages and the leads of a keyword page).
The site build
buildSite writes the whole site from a model, its fragments, a profile, a theme, a locale and a tokeniser (siteDocuments renders the same documents without writing and reports the size of the index), through assemblePages, which the gallery shares: island bundles and theme files under assets/, every page measured against SITE_PAGE_BUDGET and checked for accessibility. relativeHref, entityHref, assetsBaseOf, siteRootOf, fragmentPath, fragmentImagePath, mentionsFragmentPath and contractFragmentPath name where everything lands, every href relative to its page so that the site works over file://.
The gallery and the checks
galleryFixtures, galleryPages, galleryDocuments and buildGallery render every slot with representative data into the static page set of concordance gallery, one state per board of the reference design (GALLERY_BOARDS, the index grouping the states by board, each framed at the width of its board through a width switch island the gallery alone bundles) with the degraded cases beside them, and typePages renders every registered type it is given from its note template, through its dedicated component when the theme resolved one. skeletonOf reduces a page to its structure, the elements with their classes, role, ARIA attributes and island name, which one file snapshot per state pins under test/gallery/__snapshots__. measureBudget reports the pages over 150 kB and the size of each island. checkAccessibility runs thirteen structural and ARIA accessibility rules on any page without a browser, and checkContrast reports the text and background pairs of a theme.yaml (contrastPairs: ink, muted, label and accent text over the page, a surface and the soft surface, ink over the highlight of a mark, in both schemes) under 4.5:1 for body text or 3:1 for headings, from contrastRatio and relativeLuminance. The tests under test/accessibility/ verify six criteria on every page of the gallery, the type pages included: contrasts, colour never alone, targets of 40 px, text first, headings and keyboard, the audit on every state; the accessibility guide says what each one checks and what a theme author must keep.
Dependencies
| Package | Why |
|---|---|
| preact | the component model of the site: small enough to hydrate an island without a framework runtime worth mentioning, with a JSX runtime the TypeScript compiler targets directly |
| preact-render-to-string | renders the components to static HTML at build; the published pages need no JavaScript to be read |
| esbuild | bundles and minifies one module per island with content-hashed names; deterministic output, no configuration file |
| pdfjs-dist | the legacy build of pdf.js, bundled as the on-demand viewer and its worker; the same exact version as the converter plugin, which extracts text with it, so that one pdf.js reads and shows every document |
| unified, remark-parse, remark-gfm, remark-frontmatter | the markdown parser of the ingest package, so that a note is read the same way by the inference and by the rendering: tables, task lists, strikethrough and footnotes, the frontmatter recognised and removed |
| remark-rehype, rehype-sanitize, rehype-stringify | the markdown syntax tree turned into HTML and sanitised with the default schema before it is written: raw HTML, scripts, event handlers and unsafe URL schemes never reach a page |
Development only:
| Package | Why |
|---|---|
| axe-core | the accessibility audit the tests run over every gallery page; the reference engine, whose rule identifiers the documentation cites |
| happy-dom | the DOM axe-core needs, as a Vitest environment; no layout engine, so the contrast rules are disabled there and the palette is checked by numbers instead |
| @types/mdast | the types of the markdown syntax tree the renderer splits into sections |
