Registry and content
Every fact about an app is written once, in the registry under content/, and the pages, headers, client index and tests are generated or checked from it. This page explains the registry's files, the shape of an entry, where names and copy live, the route map, the generated client index and the landing export, and ends with a checklist for adding or changing an app.
Why a registry
The registry is the single record of every app (design system §11.2 in docs/design/suite-design-system.md; shared contracts §1 in docs/design/shared-contracts.md). It is read by:
- the shell and the workspaces (through the light client index, below);
- the landing, product, docs, task, changelog and pricing pages;
- the Content Security Policy generator (
content/csp.ts), so an app can contact only the hosts its entry declares; content/redirects.tsandcontent/apps/next-config.ts, which turn entries into Next.js headers, rewrites and redirects (see Architecture);- the generators
scripts/app-index.mjsandscripts/accents.mjs; - the tests, chiefly
content/apps.test.ts, which enforces the copy, licence and shape rules.
The rule "nothing about an app is typed twice" is enforced in two ways: derived files are generated and checked for staleness, and values that must agree in two places (for example docsPath and docsHref(slug)) are tested against each other.
How the registry is put together
| File | Role |
|---|---|
content/app-names.ts |
SUITE_NAME, BENCH_NAME, APP_NAMES and APP_SHORT_NAMES. The only file holding product names. LaunchAppSlug is keyof typeof APP_NAMES. |
content/apps/identity.ts |
APP_IDENTITY (accent and mark for every app, launch and planned, in registry order), APP_ORIGINS (working title, kind, workspace group and, for apps carried over from the maker's site, the old /tools/<slug> path) and withIdentity(), which joins identity to content. |
content/apps/<slug>.ts |
The app's content: entry: AppEntryContent (every AppEntry field except the identity fields) and page: AppPageCopy (product-page copy that is not a registry field). |
content/apps/<slug>.entry.ts |
export const app = withIdentity("<slug>", entry): the app's full entry, importable without the rest of the registry. |
content/apps.ts |
The types, ACCENTS, STATUS_LABEL, KIND_LABEL, RESERVED_TASK_IDS, the apps array in registry order, plannedApps, landingApps, APP_SLUGS and the helpers getApp, tasksFor, allTasks, allExplainers, hostsFor, sourceFor, provenanceSource and provenanceSources. |
content/apps/entries.ts |
loadEntry() and loadEntries(): one import() per app, for pages that need another app's full entry. |
content/apps/app-index.ts |
The light client index, re-exporting app-index.generated.ts with LAUNCH_SLUGS, ALL_SLUGS, isLaunchApp, indexEntry and sharedStatus. |
content/apps/pages.ts |
APP_PAGES, APP_WRITE_UPS, APP_DOCS, APP_CHANGELOGS, REQUIRED_DOC_SECTIONS, latestChanges() and the sitemap paths. Kept apart from the registry so the registry does not carry page text. |
content/apps.ts, content/csp.ts, content/redirects.ts, content/apps/routes.ts and content/apps/next-config.ts use relative .ts imports and no aliases, so next.config.ts, the Node scripts and the unit tests can import them directly.
plannedApps is empty today and PlannedAppSlug is never. A planned app would have minimal fields (PlannedApp) and no pages; cross-links into it must be marked "planned".
landingApps is apps without entries that set landing: false. Cell sets it (D-0021), so it keeps its pages and workspace but stays off the landing grid. catalogueApps is landingApps plus any entry that also sets landingCatalogue: true (Patent, D-0103; the CPO's ruling of 20 October 2026, §7), in registry order: it feeds the landing page's tool catalogue and /landing.json apps.items, while the hero tiles, the family navigation, counts and latest changes still omit the app. The type (AppLandingPlacement) makes landingCatalogue without landing: false a compile error.
The registry split
Client code must not import values from content/apps.ts, because doing so would ship every app's full entry in every workspace (the CTO's ruling of 6 October 2026, docs/board/memos/2026-10-06-cto-registry-split.md). eslint.config.mjs enforces this with no-restricted-imports:
| Code | May read |
|---|---|
src/components/suite/**, src/lib/** |
The light index (@content/apps/app-index), or entries via @content/apps/entries. Never one app's *.entry.ts. |
src/apps/**, src/components/tools/** |
The light index, the app's own @content/apps/<slug>.entry, or @content/apps/entries. |
Server routes in src/app/, *.marketing.tsx, tests, scripts |
The registry directly. |
Type-only imports of the registry are always allowed.
The AppEntry shape
AppEntry is defined in content/apps.ts. The fields fall into these groups.
| Group | Fields | Notes |
|---|---|---|
| Identity | slug, name, shortName, workingTitle, kind, workspaceGroup, accent, mark, legacyPath |
Added by withIdentity() from content/app-names.ts and content/apps/identity.ts; never written in the content file. |
| Release | status, version, updated, themes, landing, landingCatalogue |
status is alpha, preview, beta, ga or planned. version is shown in Settings and exports. themes lists "dark" to allow the dark theme. |
| Shell | shell, shortcuts, coachMarks, units, deepLinkParams |
shell sets the rail kind, inspector, stage, status chips and the top bar's orientation label. Shortcuts may not use keys the shell reserves. |
| Product page | tagline, summary, capabilities, features, howItWorks, example, preview, keywords, pricing |
tagline is one sentence of at most 120 characters. pricing is indicative and every price is null, so no tier gate is enforced. Since the CPO's ruling 2 of 17 October 2026 the product page (src/app/(marketing)/[app]/page.tsx) has five sections, hero, preview, features, sources-limits and open, checked in order by e2e/marketing/product-pages.spec.ts; howItWorks and the page's faq render on the app's docs page instead, and capabilities and pricing are no longer shown on it. No page reads capabilities now (only content/apps.test.ts); the per-app pricing is shown only in the dev kit, and /pricing keeps the suite's tiers. |
| Limits and statements | limits, limitMeta, notMedicalAdvice, statements, regulatory |
limits start with detail.limits from content/tools/<slug>.ts, then the app's own; limits[0] is the banner sentence. limitMeta is index-aligned. |
| Sources and licences | sources, sourceIds, sourceMeta, excludedSources, flags |
See below. |
| Network | dataPolicy |
See below. |
| Links and objects | crossLinks, objects |
See below. |
| Project files and imports | projectFile, imports |
See below. |
| Pages | tasks, explainers, changelogPath, docsPath |
changelogPath and docsPath must equal changelogHref(slug) and docsHref(slug). |
| Verification | oracles, verificationSets |
Offline tools used to check the numbers or build data (never shipped), and the published fixture sets for a release under public/verification/<slug>/<version>/. |
| Routing | rewrites, cacheRules, legacyRedirects |
Spread into the Next.js config. content/redirects.test.ts checks that each stays inside the app's own paths (/<segment>, /data/<slug>/, or its legacy path). Today Calc and Plasmid declare cacheRules for hashed data files and Cell declares legacyRedirects. |
dataPolicy and endpoints
dataPolicy (DataPolicy) declares how the app computes and which hosts it contacts besides its own origin.
| Field | Purpose |
|---|---|
compute |
"browser" or "browser+public-apis". |
storage |
"browser-only". |
endpoints |
The complete list of external hosts (AppEndpoint[]); an empty list is valid. |
addressBar |
The fixed address-bar sentence, ADDRESS_BAR_SENTENCE from content/apps/security.ts; the tests require every app to use it. |
wasm |
true adds 'wasm-unsafe-eval' to script-src (Molecule and Assay, which run RDKit). |
cspAllowances |
unsafeEval and dataConnect, each a sentence giving the reason, which the security page prints. Protein and Cell declare both for the Mol* viewer. |
identifiersWord, authored, linkContents |
Slots in the privacy statement (contracts §2.1). |
Each AppEndpoint has a bare host (no scheme or path), a service key for the rate limiter and circuit breaker, the operator, purpose, what the host receives, the provider's terms, and a consent rule: "none" for an ordinary lookup, "batch" after a dialog listing what will be sent, "always" when the user confirms each lookup. Optional fields are cspAlso (also add the host to img-src), budgets (published request counts), note and workspaceOnly (left out of the object pages' policy because the app's object tab never contacts it).
The same list feeds three places: connect-src in the app's CSP (hostsFor and content/csp.ts), the host table in the privacy statement, and the shell's network allow-list (configureNet({ hosts: hostsOf(app) }) in AppShell). A new API therefore has to be declared, and so becomes visible on the product page and in Settings, before the browser will let the app call it. See Security and network.
Sources and licences
sourcesis a list ofToolSourceobjects (content/apps/types.ts:name,href,licencein the provider's words,use, and optionalcredit,publisher,notices). It starts with the same objects asdetail.sourcesincontent/tools/<slug>.ts, then the app's own.sourceIdsgives each source a stable id, index-aligned withsources.sourceMetais required and has exactly oneSourceMetarecord per source id:licenceId(theLicenceIdunion insrc/components/suite/data/provenance.ts),licenceReadOn(an ISO date no later than the app'supdated), and optionalshareAlike,displayOnly,noticeRequiredanddecision. A share-alike source must also setnoticeRequired. A source whoselicenceIdisno-reuse-licenceneeds a register decision (D-NNNN) ordisplayOnly.- The licence words in
sources[].licencemust match the machine id;content/apps.test.tsholds the table of expected patterns. excludedSourceslists sources left out on licence grounds, each with its reason.flags.shareAlikeDatamarks an app that carries share-alike data.
Sources no app owns are recorded in the registry's services section (content/apps/services.ts, re-exported from content/apps.ts): suiteServices for hosts the site's server contacts, and suiteDatasets (SuiteDataset) for data the suite ships, each with its sourceMeta. A dataset downloaded only by a pipeline script sets contactedFrom: "build" with its buildHost, releaseDate and snapshotDir (the Cellosaurus cell line snapshot, E10; its release and directory are read from src/lib/cells/snapshot-manifest.ts, which the build script writes); content/services.test.ts checks that such a host is in no app's endpoints and no policy.
During development a new source may carry the licence line "To be checked" (D-0014). The existing precedent, the NNDB table in content/apps/sequence-toolkit.ts, records it as cited-data until the General Counsel reads it. scripts/licences.mjs (also run under npm test) adds registry checks: every source has a licence record, every endpoint has terms, and every licence page was read within twelve months of the app's updated date (of today's date with --release).
Objects, cross-links and hand-off parameters
objects (AppObjects, build-spec-objects §2 in docs/design/build-spec-objects.md) says which shared object types the app owns (fetches public facts for), reads (opens, so Send to lists it), writes, and shows tabs for on an object page. The code side is in src/apps/objects.ts (objectInbound, objectTabTypes) and src/apps/object-tabs.tsx; src/apps/objects.test.ts checks that code and registry agree. The object and study pages are served the union of the CSPs of every app that lists tabs (objectPageCsp in content/csp.ts).
projectFile and imports are optional (docs/design/project-file-and-grid-contracts.md §1 "The count rule" and §4 "Registration"; the CTO's rulings 4a and (iv) of 12 October 2026). projectFile.records lists the record types an app writes into a project file, each named <slug>/<type> (the key of RECORD_TYPES in src/lib/project/record-types.ts), with max, how many of that type one file may hold (a positive integer, or null for any number); the project-file reader (src/lib/project/file.ts) enforces it for the opening app's own types, and changing a declared max is a new project-file kind. Assay declares assay-analyser/assay and Sequence sequence-toolkit/record, each with max: 1. imports lists the import kinds an app's detectors declare (kind as <slug>/<kind>, with lower-case dotted extensions and mediaTypes as hints), for the suite detector (src/lib/import/detect.ts) and the file drop's accept list; no app declares any until its src/apps/<slug>/import/detectors.ts exists. Both are projected into the light client index, so one app can name a kind or record type another app owns without loading that app's entry. The record type is typed `${string}/${string}`, so a short name such as "assay" does not compile (the CTO's Stage B architecture ruling of 12 October 2026, Q1). content/apps.test.ts checks the slug prefix, unique kinds, the count values and the extensions, and holds the compile-time check; the checks against RECORD_TYPES and the detectors come with those modules.
crossLinks (CrossLink) are links into another app: to, from (what is passed, in words), params, status ("live" or "planned") and an optional note. The tests require that params are the destination's deepLinkParams, that every cross-link, deep-link and example key exists in HANDOFF_PARAMS (content/apps/params.ts), that example values pass the key's validator, and that keys which travel only after the # (FRAGMENT_ONLY_KEYS in src/lib/handoff/params.ts) never appear in an example or a cross-link.
Tasks and explainers
tasks are indexable, prerendered task pages at /<segment>/<task> (AppTask: id, title, description, keywords). The registry test requires kebab-case unique ids that are not in RESERVED_TASK_IDS, not an app segment and not a metadata route name; titles of at most 60 characters with no final full stop; descriptions of one sentence and at most 160 characters; at least one keyword. src/apps/page-ids.test.ts also checks that no task id is a workspace page id. The component for each task comes from src/apps/<slug>/tasks.marketing.tsx, registered in TASK_MODULES in src/apps/tasks.ts; a listed task with no component renders the page's fallback. Each app with tasks gets one header rule for them (taskHeaderRules).
explainers use the same shape and are served at /<segment>/explainers/<id>. Their text is loaded through EXPLAINER_MODULES in src/apps/explainers.ts; only the plasmid map has explainers today.
Methods and checks sheets
Each app's methods and checks dialog (D-0085; Shared shell, DS §6.35) reads one content module, content/apps/<slug>.methods-sheet.ts, which exports sheet: MethodsSheet (content/apps/methods-sheet.ts). A sheet only groups records that already exist: each SheetGroup is a calculator, analysis, tool, view or step, with opens (the ids definition.methods.group() can return that select it; default its own id; none for a task-page group), a title, a sentence (summary, or task, a registry task whose description says it), the inspector's equation where there is one, the methods its result goes through (the app's MethodRef records), checks quoted from the docs page's definitions through docsCheck(entries, term) (which throws if the entry is gone), and limits by limitMeta id. The data apps (Protein, Pipeline, Cell, Patent) have no method records, so their groups carry checks and no level. The shared module also holds the dialog's words, the three levels' definitions (the CSO's ruling of 14 October 2026; the words themselves are provenanceUiTexts.verification in content/apps/data-ui.ts, which the chips use too), and the pure helpers lowestLevel, belowTop, groupFor, groupForMethod, setsFor, oracleNamed, limitText and summaryOf.
The data apps' docs pages, and Sequence's, Calc's and Assay's (Calc and Assay since the CPO's E6 ruling of 17 October 2026; Plasmid's comes with Stage C and Bioreactor's with its owner), keep their Methods and Verification definitions lists in content/apps/<slug>.docs-entries.ts (methodsEntries, verificationEntries, lists of { term, detail } where each term is a method name or a test file path; Pipeline's defaultEntries and precedentEntries), modules with no imports, or one light import of a module with none (Sequence's structure badge, Calc's master-mix sentence, Assay's oracle versions), that the docs page and the sheet both read: a sheet that imported the docs page would load the tool write-up (content/tools/<slug>.ts) with it, and the bundler would then ship the whole write-up in the app's definition chunk (Protein's went from 18.3 to 20.3 KB before the split).
src/apps/methods-sheets.ts (METHODS_SHEETS) loads each sheet with a literal import(), so no workspace carries one before its dialog opens. Calc's calculator-to-methods map is content/apps/lab-calculators.method-groups.ts (METHODS_BY_TASK for the method inspector, METHODS_BY_OPERATION for the dialog) and Sequence's is content/apps/sequence-toolkit.method-groups.ts (METHODS_BY_OP, re-exported by src/apps/sequence-toolkit/rows.ts). Assay's sheet keeps the dose-response path (DOSE_RESPONSE_PATH) in every group beside the fit, and adds the core records a mode's result also goes through (MODE_CORE).
The same sheets make the public methods pages (/methods, /methods/<segment>; D-0085): src/lib/methods/markdown.ts writes each app's sheet as Markdown with every group expanded, the groups first and the limits last (appMethodsMarkdown), the overview with each group's lowest level (methodsIndexMarkdown), what the levels mean on its own page, /methods/levels, which the others link to rather than repeat (levelsMarkdown), and /llms.txt (llmsTxt, the methods pages only). The pages say how a result is reached and checked, not how the app is built (the CEO's instruction of 14 October 2026): each method shows its name, level, reference and how it was checked, never its implementation path or version; the published verification sets, their expected-value files and the reference packages' table are left out; a check titled by a file shows its text alone; and scrub takes file names out of record text (a parenthesis naming a file goes, a file checked against reads "stored reference values"). The dialog and the docs pages keep the full records; the pages render it with the technical documentation's renderer (renderTechnicalDoc with a resolver that keeps site paths), and the index.md routes serve it with absolute links. Their frame's words are in content/apps/methods-page.ts; the addresses are methodsHref, methodsLevelsHref, methodsMarkdownHref, methodsLevelsMarkdownHref and llmsTxtHref in content/apps/routes.ts (no app may take the segment levels). src/lib/methods/markdown.test.ts checks that every group and method is on its app's page, that each page opens with its first group and closes with its limits and links to the levels page, that no page names a source file, implementation version, verification file or reference table, that the overview's links reach each group's heading (groupAnchor, the renderer's headingId, now in src/lib/technical-docs/heading-id.ts), that no link is lost in rendering, and that /llms.txt lists every app.
src/apps/methods-sheets.test.ts checks that every app has a sheet; that every id the workspace can have open selects exactly one group; that every method record an app keeps is in a group; that every record checked against a reference has its note; that every group has a title, a sentence and only the registry's limits; and that Assay's groups beside the fit hold its whole path. Which methods a group's result goes through is a scientific claim: the CSO reviews a change to a sheet (the CSO's review of 14 October 2026, docs/board/memos/2026-10-14-cso-methods-dialog.md).
Product names
Product names appear only in content/app-names.ts. Components read name and shortName from the registry or the client index; slugs, storage keys and URLs never contain a product name. This is enforced by:
content/apps.test.ts("no product name appears outside content/app-names.ts"), which scans every.ts,.tsx,.mts,.mjs,.js,.css,.mdand.jsonfile undersrc/andcontent/;content/apps/routes.test.ts, which checks that no product, workspace, docs or social-image address contains the suite name or a product name;scripts/app-index.mjs, which writes names into the generated index by reference (APP_NAMES["<slug>"]) and refuses to write any indexed value containing a product name.
A rename is therefore a change to content/app-names.ts alone. The names are working names until the trade-mark checks are done.
Copy lives in content files
User-facing words are kept in content/, never in components:
| Copy | Where |
|---|---|
| Product-page copy (hero lede, trust tile, FAQ, transcript) | page in content/apps/<slug>.ts. The hero lede and transcript show on the product page; the FAQ shows on the docs page (with the shared How it works and Keyboard blocks, after the app's own sections); security.posture, the trust tile's text, is shown on /security only since the trust tiles left the product page (17 October 2026) |
| Tool write-up, base limits and sources | detail in content/tools/<slug>.ts (ToolDetail) |
| Methods and checks dialog | content/apps/<slug>.methods-sheet.ts (MethodsSheet) and the shared words in content/apps/methods-sheet.ts; see Methods and checks sheets |
| Docs page | content/apps/<slug>.docs.ts (AppDocs), with the definitions lists in content/apps/<slug>.docs-entries.ts for every app but Plasmid and Bioreactor; must contain the methods, exports, verification and security sections (REQUIRED_DOC_SECTIONS, checked by content/suite-pages.test.ts) |
| Changelog | content/apps/changelog/<slug>.ts: at least one entry, newest first, no date after the app's updated |
| Workspace and shell copy for one app | Further content/apps/<slug>.*.ts modules, for example plasmid-map.shell.ts and plasmid-map.workspace.ts |
| Suite-wide copy | Modules such as content/apps/suite.ts, shell.ts, landing.ts, objects.ts, security.ts, shared-texts.ts, regulatory.ts, and content/education*.ts |
The tests apply copy rules to this text: no em dashes and no exclamation marks, taglines and summaries within their limits, the forbidden regulatory phrases defined in content/apps/regulatory.ts (scanned over content/ and src/ by scripts/forbidden-phrases.mjs), and no internal vocabulary such as board seat names in strings under content/apps (content/apps/user-copy-jargon.test.ts).
Release notes (the changelog)
The rule for content/apps/changelog/<slug>.ts (the CPO's E6 ruling of 17 October 2026):
- One entry per registry version. An entry's
versionequals the version the registry gave the app when the entry landed, and no two entries share a version. - No entry without a version bump. A change that does not bump the registry's
versionadds no entry; a bump adds exactly one. - Dated the day it reached
main, not the day it was written or branched. - The title says what a scientist can now do. The body says what was added, changed and removed, and what was checked against what (the fixture and the named oracle, as the docs page's Verification section names them).
- Versions are never invented. Where the registry's version has not moved although the app has (Assay's registry says 0.2.0 while Stage A has shipped since), the owner raises it with the coordinator; a builder does not write the missing entries or bump the version to fit them.
The rule is not yet a test, because some changelogs depart from it today: Sequence has two 0.2.0 entries; Protein, Molecule, Pipeline and Cell have entries without a version (Pipeline's and Cell's newest entries among them, with the registry at 0.1.0). Each is its owner's to correct with its next release; content/apps.test.ts keeps its existing checks (at least one entry, newest first, no date after the app's updated).
The route map
content/apps/routes.ts builds every address in one place, so moving the suite is a change to that file and the route folder names. The suite sits at the root of its own origin (SUITE_BASE = ""); LEGACY_BASE = "/apps" is the prefix the legacy redirects start from.
APP_SEGMENTS maps each launch slug to a short URL segment (protein, molecule, pipeline, cell, calculators, sequence, assay, plasmid, bioreactor). slugForSegment() reverses it for the [app] route parameter. content/apps/routes.test.ts requires the keys to be in registry order, segments to be unique kebab-case words, and none to be a reserved word such as docs, data, vendor or apps. Slugs remain the machine ids for storage keys, data-app, accents and changelog anchors; only addresses use segments.
| Helper | Address |
|---|---|
landingHref(anchor?) |
/ |
productHref(slug, anchor?) |
/<segment> |
appHref(slug, params?, via?) |
/<segment>/app, with query parameters; via is always last |
appPageHref(slug, page) |
/<segment>/app/<page> |
taskHref(slug, taskId) |
/<segment>/<task> |
explainerHref(slug, id?) |
/<segment>/explainers[/<id>] |
docsHref(slug?, section?), changelogHref(slug?) |
/docs[/<segment>], /changelog[#<slug>] |
pricingHref(), securityHref(), regulatoryHref() |
Suite-wide pages |
objectsHref(), objectHref(type, id), mineObjectHref(type, uuid), studyHref(id) |
/o, /o/<type>/<id>, /o/mine/<type>/<uuid>, /study/<id> |
literatureHref(query?), educationToolHref(id) |
/literature/app#q=..., /<id>/app |
ogImageUrl(slug), posterUrl(slug, theme = "light") (/media/<slug>/preview-<theme>.webp), verificationFileUrl(...), schemaPath(...), noticesHref() |
Files and images |
absoluteUrl(path) |
The path on SUITE_ORIGIN (content/apps/maker.ts) |
The module also exports the header-rule sources the config uses: workspaceRouteSource, taskRouteSource, OBJECT_ROUTE_SOURCES, OG_IMAGE_SOURCE and OG_IMAGE_DESTINATION. Always build addresses with these helpers rather than string literals.
The generated client index
content/apps/app-index.generated.ts holds, for each launch app, only the fields shared client code reads: slug, name, shortName, workingTitle, workspaceGroup, mark, status, objects, sourceIds and deepLinkParams, and projectFile and imports where the app declares them (INDEX_FIELDS and OPTIONAL_INDEX_FIELDS in scripts/app-index.mjs, which records who reads each field). Planned apps carry slug, workingTitle, mark and status. The file also exports STATUS_LABEL.
- Do not edit it by hand. Run
npm run app-indexafter changing an indexed field, and commit the result. - The prebuild writes it too (
scripts/prebuild/suite-app-index.mjs), so every build ships an index that matches the registry. node scripts/app-index.mjs --checkexits with status 1 when the file is out of date.content/apps/app-index.test.tsfails undernpm testwhen the committed file differs from what the generator would write. It also checks that each indexed field equals the registry's, that each<slug>.entry.tsexports the registry's own entry object, and that the generator refuses a name not taken fromcontent/app-names.ts.
src/components/suite/accents.css works the same way: npm run accents writes it from ACCENTS and APP_IDENTITY, and src/components/suite/tokens.test.ts fails when it is stale.
The landing export
mbio.tech/apps shows a copy of this site's landing page, read from /landing.json. The route is src/app/landing.json/route.ts: force-static, built with the site, with Access-Control-Allow-Origin: *. Both page and export use src/app/(marketing)/landing-data.ts and shared content.
LANDING_EXPORT_VERSION is 7 (23 October 2026). D-0132 (24 October 2026) reorders sectionOrder's values to hero, guided-example, trust, how-it-works, apps, ai-integration, education; no field is added, renamed or removed, so the version stays at 7 and the consumer renders in the order the value gives. D-0133 (24 October 2026) changes values only: trust.items gains a third claim ("Runs in your browser"), hero.actions.secondary and each example's action read differently, and aiIntegration.benefits is still exported although the page no longer renders it, so the version stays at 7. Version 7 adds guidedExampleSection.medicinal and appends Medicinal chemistry after Assays and screening and Molecular biology. It retains version 6’s ordered example and step contracts. The third example summarises three stages within the molecule app, with source attribution and scientific qualifications in existing note and limits fields; it adds no scientific numeric export fields. Its actual nearest-walk link uses the existing helper and is enabled only with the published scientific reference and affected walk checks. The primer action remains separately gated. Version 6 changes guidedExample from one object to an ordered list and adds guidedExampleSection for the shared heading, lede and switcher labels. Each entry has a permanent fragment id and work label; only an activated guided walk supplies href and action. The primer-pair example omits both until D-0114's recorded rebuild and daily-matrix requirements pass. The hero's secondary action now targets #guided-example. The top-level fields are version, suiteName, sectionOrder, hero, guidedExampleSection, guidedExample, aiIntegration, apps, workspace, education and trust. Version 5 moves one field: the storage line is workspace.storage (shown in the Bench section, under its lede) and trust.storage is gone; every other field is kept. In the same revision, values changed without a change of shape: sectionOrder is hero, guided-example, apps, how-it-works, education, ai-integration, trust; apps.groups runs discovery, bench, literature; trust.items holds two claims (methods and reference checks; "Start in your browser" went), each with its own icon; and the workflow, hero trust line, Bench lede and trust strings are D-0112's. Version 4 added aiIntegration, apps.sharedTools and the catalogue copy fields literatureHeading, literatureDescription, literatureIcon and patentDescriptor. The catalogue includes Patent among registered items, by its registry field landingCatalogue, and Literature among shared tools.
The complete field inventory below uses [] for an array. Values are strings unless the source indicates an array, object, number or nullable media. Copy and identifiers are supplied by the named sources, not duplicated in the renderer.
| Object | Fields and nested shapes |
|---|---|
hero |
kicker, title, headlineLines[], headline, status, lede, actions: { primary: { label, href }, secondary: { label, href } }, trustLine, familyLabel, workspaceTile: { label, accessibleLabel, href }, figure |
guidedExampleSection |
id, kicker, heading, lede, navigation, assays, molecular, medicinal |
guidedExample[] |
id, role, heading, lede, example, steps[]: { slug, title, body, handoff }, note, limits[]; optional href and action together, only for an active guided walk |
aiIntegration |
id, status, heading, lede, availabilityNote, benefits[]: { title, body }, steps[]: { title, body }, engineLabel, responseFields[], diagramNote, localPreview as below; the figure's conversation copy is not exported |
aiIntegration.localPreview |
status, heading, lede, action, href, setupNote, tools[]: { appSlug, label, id, appName } |
apps |
id, kicker, heading, lede, groups[]: { id, label }, literatureHeading, literatureDescription, literatureIcon, patentDescriptor, open, about, openLabel, aboutLabel, descriptions: { [appSlug]: description }, items[], sharedTools[] as below |
apps.sharedTools[] |
id, name, href, description, icon, group |
apps.items[] |
slug, name, shortName, group, description, accent: { base, deep, glow, hue } (hue is numeric), mark: { key, paths[] }, productPath, workspacePath, docsPath |
workspace |
id, kicker, heading, lede, storage, points[]: { title, body }, action, href, previewLabel, previewNote, preview: { study, steps[]: { label, title, body } }, scope |
education |
id, kicker, title, lede, open, browse, note, featured[]: { id, description, caption, title, href, media }, groups[]: { id, title, links[]: { id, title, href } }, dosePlot: { label, description, response, concentration, curve, readings } |
education.featured[].media |
null or { src, alt, width, height, sourceLabel, sourceHref }; dimensions are numeric |
trust |
id, kicker, heading, items[]: { title, body, link, href, icon }, regulatory, regulatoryLink, regulatoryHref (the page's closing band; icon is not drawn on the page) |
hero comes from content/apps/suite.ts with shared landingActions. guidedExample maps the same landingExamples list that the page renders, including each example's public steps, introduction, role and source note. guidedExampleSection comes from content/apps/landing-examples.ts. Scientific limits[] accompany the primer prediction and structural-similarity example in the export; the latter retains ChEMBL 37/EMBL-EBI CC BY-SA 3.0 share-alike, input-source and recorded source/licence dates in note. Guided actions are projected only when example.walk is present, so a consumer cannot expose an unavailable walk. The list keeps version 6’s shape; version 7’s new field is the medicinal section label. In its steps, a number and its unit are joined with a no-break space (U+00A0, keepUnits in content/apps/landing.ts; the CDO's merge review of 21 October 2026), which changes bytes, not shape. apps, workspace and trust use content/apps/landing.ts. App items come from the shared server-side marketing/landing-catalogue.marketing.tsx adapter, which reads the registry's catalogueApps (registry order, so Patent follows Pipeline; before 20 October 2026 the adapter appended it last). An app listed through landingCatalogue reads its description from APP_PAGES[app.slug].hero.lede, preserving the integrated app version's qualification (for Patent, synthetic examples on this base). That change did not alter the export's shape. Other descriptions use landing copy or the registry tagline. The hero still uses landingApps; neither the app registry visibility flag nor shared navigation labels change. Education comes from content/apps/landing-education.ts and its source registries/media manifest.
The same catalogue adapter supplies both the page and export with ordered groups discovery, bench, literature (landingCopy.tools.groups; Papers & patents moved last on the CEO's words of 21 October 2026, "put literature and patent at the bottom", D-0112 item 6). Patent's group is its registry workspaceGroup (literature), so the move needed no registry field and no slug in a component. The last label is Papers & patents, from landingCopy.tools.literatureHeading; the other labels reuse shared navigation copy. apps.sharedTools derives Literature’s id, name and href from SHARED_WORKSPACES, and adds its landing description, icon: "text" and group: "literature". Literature is a shared workspace, not a registered scientific app: it has an Open action but no invented About, product, docs, mark or accent fields. Consumers render shared tools before registered app items within their matching group. patentDescriptor is display copy below Patent’s registry name, not part of that name. These catalogue additions came with version 4 and must be included in the consultancy consumer update.
aiIntegration comes from content/apps/landing-ai.ts. Its id is ai-integration. The main copy and explanatory diagram describe planned hosted assistant connections, naming ChatGPT and Claude as intended targets rather than tested or available integrations. availabilityNote, status, engineLabel and diagramNote keep that future scope visible. responseFields[] contains human-facing diagram labels, not MCP protocol keys or a promised wire schema. Since 24 October 2026 (D-0134 as the CEO shaped it: a graphic, not a wall of text) the page renders the status, heading, lede and availabilityNote beside one figure (landing-ai.marketing.tsx) of three examples under tabs, Calc, Sequence and Assay (conversation.examples, each with its app, tool source key, question, two readouts, engine header and rows, reply, caption and an optional mark for synthetic data; the Sequence and Assay values from published reference sets through landing-ai-data.ts), each three labelled stages: the assistant's question, the Calc dilution tool's answer as a result card, and the assistant's reply in prose, whose every value is a workflowSlots() slot from the J3 example or a registry value (Calc's version, the tool's label and id from mcp-source), with conversation.caption beneath. The figure's copy is conversation in the same file, slot templates filled on the page; it is left out of the export, so /landing.json keeps its shape and version. benefits, steps, responseFields, diagramNote, methodsLine, checksNote and toolsLabel stay exported and unrendered; the page names no tool, since the hosted set is not complete. The four implemented tools and developer setup are nested under localPreview, rendered as a closed native disclosure; localPreview.href is /technical/mcp-server. No hosted action or connection URL is exported. marketing/mcp-source.ts reads the selected tool source files at build time and extracts their exported identifiers, refusing a missing or ambiguous declaration. The content's source filenames are not exported. Tool labels and app-slug associations remain in content, app names come from getApp, and tool identifiers remain in their implementations. mcp-source.test.ts compares the public inventory to createServer().listTools(); only the test imports the running server. The website imports no MCP implementation, scientific engine or RDKit through this adapter.
The homepage setup link is the narrow exception to D-0081 authorized by the CEO's MCP marketing request of 18 October 2026; it does not change the technical area's noindex/security policy or close Q-17. See Local MCP server for the development-preview boundary.
When adding, renaming or removing an exported field, bump LANDING_EXPORT_VERSION and tell the consultancy owner the complete shape. mbio.tech keeps its last compatible snapshot until its renderer supports version 7 (including the version 6 ordered-list migration); an hourly refresh alone cannot adopt the new schema. Copy-only changes need no version bump. Keep the landing unit checks and e2e/marketing/suite-pages.spec.ts synchronized with the version and rendered/export order. This schema documentation does not claim those checks have passed on the current head.
Adding or changing an app
The steps below follow from the types, the Record<LaunchAppSlug, ...> maps and the tests. Adding a slug to APP_NAMES makes npx tsc --noEmit list most of the maps that still need an entry.
Identity and names
- Add the product name and short name to
APP_NAMESandAPP_SHORT_NAMESincontent/app-names.ts. Key order is registry order: the tests requireAPP_SLUGS(the order ofapps) to equal the keys ofAPP_NAMES. The short name must end the full name, be unique and be at most 12 characters. - Add the accent and mark to
APP_IDENTITY, and the working title, kind and workspace group toAPP_ORIGINS, incontent/apps/identity.ts. Every app has its own mark. Two apps next to each other in registry order may not share an accent, and the four carried-over apps never share a hue (content/apps.test.ts). A new mark needs aMarkKeyincontent/apps.tsand a drawing inMARKSinsrc/components/suite/mark.tsx(src/components/suite/marks.test.ts). - Add the segment to
APP_SEGMENTSincontent/apps/routes.ts, in registry order.
Registry entry and copy
- Write
content/tools/<slug>.tsexportingdetail: ToolDetail. - Write
content/apps/<slug>.tsexportingentry: AppEntryContentandpage: AppPageCopy, andcontent/apps/<slug>.entry.tscallingwithIdentity. - Add the entry to
appsincontent/apps.tsand a loader tocontent/apps/entries.ts. - Write
content/apps/<slug>.docs.tsandcontent/apps/changelog/<slug>.ts, and register the page, write-up, docs and changelog incontent/apps/pages.ts. Add the app to theDETAILS,PAGESandCHANGELOGSmaps at the top ofcontent/apps.test.ts. - Add the app's title to
ctaBand.product.titlesincontent/apps/suite.ts. Landing descriptions (content/apps/landing.ts) and showreel captions (content/apps/showreel.ts) are optional. - Declare every external host in
dataPolicy.endpoints, with consent and terms; setwasmandcspAllowancesonly when needed, with reasons. - Give every source an id and a
sourceMetarecord whoselicenceIdmatches the licence words; list excluded sources with reasons. - Make sure every
deepLinkParams, example and cross-link key is inHANDOFF_PARAMSincontent/apps/params.ts.
Code
- Create
src/apps/<slug>/definition.ts,entry.tsx(with adisplayNameofdrybench-entry:<slug>),page-ids.tsandobjects.ts, and the domain code insrc/components/tools/<slug>/. - Register the app in
src/apps/definitions.ts,src/apps/page-ids.tsandsrc/apps/objects.ts; insrc/apps/object-tabs.tsxif it listsobjects.tabs; insrc/apps/tasks.tsif it has tasks; insrc/apps/explainers.tsif it has explainers. - Some Workspace modules name apps individually where they take part in a shared feature (for example
OBJECT_BACKED_WORK_TYPESinsrc/lib/workspace/work-apps.ts). Search for an existing slug to find them and decide whether the new app belongs.
Generated files and optional extras
- Run
npm run app-indexandnpm run accents, and commit both generated files. - Optionally add a size budget (
scripts/size-budgets/<slug>.json), build-time steps (scripts/prebuild/<slug>-<what>.mjs), data underpublic/data/<slug>/withcacheRulesfor content-hashed files, and published posters: the two capturespublic/media/<slug>/preview-light.webpandpreview-dark.webp, written byscripts/capture-previews.mjsfrom a production build (see Build, test and CI), plus the slug inCAPTUREDincontent/apps/posters.ts, whichcontent/apps/posters.test.tskeeps equal to the files on disk. The registry'spreview.posterisposterUrl(slug), the Light file;AppPostershows the file for the current theme with thedark:variant ([data-theme="dark"]), no script, and draws the mark poster while either file is missing. All ten apps have paired captures: the initial eight on 17 October 2026, followed by Plasmid and Bioreactor on 18 October at the CEO’s request. Their registry alt text describes the actual captured map/inspector and simulation plots.
Checks
- Run
npm run lint,npm test,npx tsc --noEmit,npm run buildand, for anything a user sees,npm run test:e2e, asCLAUDE.mdrequires. See Build, test and CI.
For a change to an existing app, the same rules apply to the fields you touch. Changing an indexed field (a mark or status, for example) needs npm run app-index; changing an accent needs npm run accents; adding a host changes the app's CSP and privacy table. version must be x.y.z, updated an ISO date, and no changelog entry or licence reading date may be later than updated.
Related pages
- Architecture: how the registry drives routing, headers and the shell.
- Security and network: the CSP and the network layer built from
dataPolicy. - Data pipeline: the data files and snapshots that sources describe.
Shared workspace tool entries (16 October 2026)
SHARED_WORKSPACES is re-exported from the registry and lightweight client
index. It lists Literature without giving it a scientific app definition.
The footer renders scientific apps and shared tools from those sources; the
Literature name comes from content/app-names.ts. /literature/app is
canonical, with a permanent redirect from /literature; old #q= fragments
continue to identify searches. Both paths use the shared workspace CSP.
Calc now has a dedicated amber accent (#8c4d0d / #f3c178), distinct from
Pipeline violet; Bioreactor retains indigo. Generated accent CSS uses the
same light/dark/paper surface rules. All contrast and hue gates remain in
tokens.test.ts; status and chart colour tokens are unchanged.
Public reference workflows (16 October 2026)
content/seo/registry.ts is the typed public-reference registry. Product directories, reference static parameters, links, sitemap and exact native CSP rules derive from it. The public Calc hub is /calc; /calculators/app retains its native address. See Public reference workflows for the shared route, state, source and export contracts.
Public introduction and motion (18 October 2026)
Product hero ledes are concise summaries in each registry entry's page.hero.lede. The hero displays copy before the actions on phones as well as desktop; the earlier mobile flex-order workaround is removed. The action row includes the shared methods link. Feature facts and sources still render from the registry; the full limitations list is available on each app’s documentation page. Patent's introduction remains scoped to the synthetic product store until the real captured store is integrated; a capture-data commit alone does not change that claim.
Since D-0112 (21 October 2026) the landing page's closing band (trust) holds two claims, methods and reference checks, with the research-use statement; the browser-access card is gone and the browser-storage line is in the Bench section (workspace.storage). Both stay visible without a click, and the illustrative Study keeps its qualification and portable recovery explanation. Decorative paths, animation hooks and timings are presentation only and are not added to /landing.json. Since D-0133 (24 October 2026) the band, now the third section, holds a third claim, "Runs in your browser" (the CSO's wording, linking to the security page), as a third trust.items value; hero.actions.secondary reads "See the worked examples"; each example's action names its work and first app; aiIntegration.benefits is still exported but the page no longer renders it; workspace.preview is still exported while the Bench section shows the Study record of landing-bench.marketing.tsx instead. Values only: the version stays at 7.
Public sales-page redesign (18 October 2026)
The About source section now follows reference templates/configured workflows. The source directory starts closed; inside it sources expand individually and network/data-policy detail has its own native disclosure. The full limitations list remains on /docs/<app> and is no longer repeated on About, on the CEO's explicit direction. The lower research-use policy, methods link and full source facts remain. Product copy additions are internal to productPage and add no export fields; the landing export's current version is documented above. Graphics and motion metadata are presentation-only. The shared source ledger still defaults to the full presentation on documentation pages.
Sequence's promoted reference-check feature now describes the published verification sets rather than claiming every method has an oracle. This is a correction in the single registry source, cleared by the CSO; no scientific implementation or fixture changes.
The About reference directory now presents up to four foundational routes in registry order as worked-example previews, with exact metaDescription, dataset.title and a dataset-kind label. Remaining routes keep their title links inside native “More examples”. The shared labels live in content/seo/interface.ts; there is no duplicate app fact or new client boundary. Expansion descriptions are not promoted before scientific review. The directory stays before source disclosures and works without JavaScript. These reference-directory changes add no fields to the version-4 landing export.
The landing refinement (18 October 2026; rebuilt on 20 October 2026 after the audit of the Gemini rewrite, then simplified to three static steps the same day at the CEO's request) renders the guided example through landing-workflow.marketing.tsx, a server component. Each step's heading and sentences are the public step texts in content/apps/landing.ts (exported as guidedExample[].steps), which quote J3_VALUES and J3_SERIES and are checked against j3Slots(). The labels of each step's key figures are content/apps/landing-workflow.ts's, outside the landing export: templates whose figures are slots, in sentence case, with no typed number, no "verified" or "verification" and no tick (landing-workflow-data.test.ts refuses them, as the CSO asked on 20 October 2026). src/components/suite/marketing/landing-workflow-data.ts fills them from j3Slots(), formula-mass.json (the atomic-weight table), dilution.json (the dilution ladder) and the published PRISM example JSON (readings, plates, the missing well's own row: PREP016_CS5_X2 well C3 at 10 µM). No structure is drawn. The plot preserves each finite series reading and exact positive published dose; it does not normalise, fit, average or impute. The native table retains all series rows, including missing MFI. The separate homepage calculator demonstration was removed at the CEO's request because its interface was not present in the app. The connected workflow remains within the guided-example presentation; version 6 adds the ordered examples and shared section metadata. Since D-0112 the scene (landing-workflow.marketing.tsx) renders whichever example it is given: landing-examples.marketing.ts lists the three examples (J3, primer-pair and nearest), and each brings its steps, a card per step (landing-workflow-j3.marketing.tsx for this one), its walk link and its source note; guidedExample exports all three entries and omits an unavailable walk action. The CSS-module switcher uses permanent :target fragments and works with JavaScript disabled; J3 is the default for every other fragment. The primer cards project primer-pair.values.ts and the captured GenBank source metadata through landing-primer-data.ts. They show the supplied-template footprints and predicted linear product features, with capped-search/template-specific and no-PCR qualifications; no Tm is displayed. Its guided-walk action remains gated by D-0114. The J3 plot retains every raw point; only the display ladder is reversed to the increasing dose direction. Axis ticks use adaptive molar units and grouped raw MFI. Formula-weight bars share a zero baseline and use the existing displayed fixture values. The export and section order, including the planned AI section, are documented above. The browser CTA now opens the tool directory; shared source copy describes declared public-data hosts without implying that every site service is local.
Molecule now registers the offline Python RDKit 2026.03.6 oracle and ten-query molecule.morgan-tanimoto set with explicit verification version 0.3.0, while app version remains 0.2.0. Publication versions are independent of app releases: the generated verification links use a set’s explicit version. The manifest and integrity/value bridge are described in Oracles and verification; no fixtures or engines are imported into the app registry entry.
Medicinal chemistry worked example (23 October 2026)
landing-nearest-data.ts fills CMO templates from NEAREST_VALUES, the published ten-query reference and its recorded manifest. The byte/hash/full-ten-neighbour bridge was committed before the landing projection. The fixed input’s fragment count comes from the independent Chem.GetMolFrags receipt. The page shows input fragment handling, library/same-compound exclusion and the first three neighbours in existing rank order with six-decimal display scores. No property map or activity series is projected; full-precision scores and all ten ranks remain in the linked reference. The JSON carries the same stage bodies, source note and limits; the table’s score rows are presentation data and are not new JSON fields.
WorkflowExample.cards is an ordered internal array corresponding one-to-one to steps. The scene rejects unequal lengths and missing cards and uses example/index keys so repeated molecule app slugs render distinct cards. The exported step keys stay exactly {slug,title,body,handoff}. Existing J3 and primer card order/output is retained. The native fragment selector appends #example-medicinal-chemistry; J3 is still first/default for missing, shared-section and unrelated fragments. No client component, engine, network request or new dependency is introduced. Nearest uses its actual guided-walk helper; primer’s action remains absent. The nearest entry requires its own existing browser checks, with no primer-specific daily-matrix requirement. Final rendered checks and activation evidence belong in the dated implementation review.
The mbio.tech owner must migrate the consumer to version 7, including guidedExampleSection.medicinal and the third ordered example, while retaining source notices, limits and optional-action handling from the outstanding version 6 migration. No external message is sent by this change.