Skip to content

Libraries ​

A library holds components, a theme, page templates, images and fonts that reports share. Reports import a library by a version range (library:billing@^1.2), and publishing a report pins the release it used, so a new release changes nothing until a report takes it up. This page is about making and changing libraries in the studio, and taking a library's component, theme or page template into a report.

The Libraries page ​

Open it from the sidebar's Libraries, Alt+2 or Go to libraries in the command palette from any page, or from the designer's Library tab (Open the libraries). Its address is /libraries/<name>?env=<environment>&tab=<tab>.

The sidebar lists the environment's libraries, each with its latest release and whether it has a draft. A library's page has these tabs:

  • Components: what the library exports. Add, rename, describe and delete components, edit their props, and open one in the component editor (Edit).
  • Theme: the tokens, chart colours and classes that reports importing the library with its theme build on, edited with the Theme panel's editors.
  • Assets and fonts: the images and fonts reports use as library:<name>/<path>. Upload them (images go under images/, fonts under fonts/) and remove them, and import and declare font families (see below).
  • Page templates: page sizes, margins, headers, footers and backgrounds that new reports and page templates start from (see below).
  • Releases: each release with its notes, who released it and when, and its copy in each environment.
  • Used by: the reports that use the library, and updating them to a release (see below).

With no library picked, the page shows a card for each library with how many reports use it, and the library browser below them.

New library… asks for a name and a description. The name is lower-case letters, digits and hyphens, starting with a letter. It can't be one that any environment already has, because promoting a release would meet the other library. The new library is an empty draft.

The library browser ​

One browser shows what the libraries offer. It is in the designer's Library tab, under In this document and Imports, and on the Libraries page under the library cards. It lists each released library's latest release, with what the release has:

  • Components, each with a thumbnail, its description and its props (a * marks a required one).
  • The theme, with its colour tokens as swatches and how many tokens, classes and defaults it has.
  • Page templates, once libraries have them.
  • Images and fonts, each with the name a report uses for it (library:brand/images/wave.svg), and a thumbnail for an image.

Each release says how many reports use the library ("used by 12 reports"): the reports whose published version pins one of its releases, or whose draft refers to it. On the Libraries page this opens the Used by tab. Release notes opens the notes of every release, newest first. A library with only a draft isn't listed, as it has nothing to import yet.

Search the library finds the items that have every word you type, in any order. It looks in names, the library's name, descriptions, prop and slot names, the theme's token and class names, and the kind of item: "theme", "page template", "image" and "font" find those. Within a release, items whose name has a word you typed come first. Library to show keeps one library, and Kind of item to show keeps components, themes, page templates, or images and fonts. Show everything clears the search and both filters.

A thumbnail is the component laid out by the engine with placeholders for its props, on a small page of its own, in its release's theme. It doesn't depend on the report that is open. Thumbnails are laid out one at a time as they scroll into view, and each is kept for the rest of the session for that release's contents, so the same release in another environment, or on the other page, isn't laid out again. A component that doesn't lay out says "No preview".

In the Library tab:

  • Insert puts the component after the selection, with placeholders for its required props, and imports it at ^<release> in the same undo step (or adds it to the library's import, if the report has one).
  • Preview lays the component out among the report's own pages and styles, as it would look once imported. Nothing is imported or inserted.
  • A component's menu (right-click, or Shift+F10) has Insert, Open (the preview) and Show the release. For a component the report imports already, it also has Make a local copy and Detach (copy and inline) (see Copy and detach).

On the Libraries page, Open takes you to the item's library: a component opens in the component editor, and the theme, an image or a font opens on its tab.

Refresh reads the libraries again. Releasing or promoting from the Libraries page refreshes them too.

Editing a draft ​

A library has one draft per environment: its working copy. The page shows the draft, or the latest release when there is no draft. Your first change starts a draft from the latest release.

The draft opens in the engine as a document of its own: its components, its theme as the document's styles, and its page templates as the document's. The component editor and the Theme panel change it with the same commands as a report, so Undo and Redo (and Ctrl+Z, Ctrl+Y) work on it. Changes are saved to the draft's files after a short pause, as a report's are, and the page says when everything is saved. Each save is a commit on the environment's branch.

A component opened in the component editor is laid out on its own, with preview values for its props, as in a report.

Releasing ​

Release… compares the draft with the latest release and lists what changed. From that, it suggests the next version:

ChangeVersion
A component, prop, slot, part, report, the theme, a font family or one of its styles, or a file is removed, or a prop becomes required (a new required prop too)Major
Anything is addedMinor
Anything else (a template, a style, a token's value, a description, a page template removed, a font family updated from its source)Patch

Removing a page template is a patch, since no report imports one: reports have copies of it.

You can change the version. It must be a new major.minor.patch, since releases can't be changed. The notes start as the list of changes, and you can edit them. The engine checks the library before it is released, each page template too, and its errors stop the release.

On the Releases tab, Promote to environment copies a release to the next environment that lacks it. Promoting needs the licence's environments and library.release in the target.

Page templates ​

A library's page templates are RFML page templates (spec §9.1): a size and orientation, margins, and a header, footer, background and overlay, with variants for first, left, right, last and blank pages. They are starting points. A report gets a copy of the one it starts from, so changing or removing it in the library later changes no report. Nothing imports a page template.

On the Page templates tab:

  • New page template asks for a name, a size and an orientation, and starts it with 20mm margins. The name is lower-case letters, digits and hyphens, starting with a letter, and it is also the template's id in the reports that copy it. page is kept for the page a library's components are laid out on.
  • Edit opens it in the page template panel, as a report's are edited: size, orientation, bleed, margins, footer distance, variants, and the nodes in each band. Rename there renames it in the library. Done goes back to the list.
  • Delete removes it from the draft. Reports that copied it keep their copies.

Each change is one undo step of the draft, saved to pages/<name>.json, and the manifest lists the templates under pages. A release checks each page template as a document of its own, with the library's theme and components, so a header that names a class the theme has, or uses one of the library's components, is fine.

Classes, tokens, components and declared images a page template uses come from the library. A report that copies the template gets them only by importing the library (with its theme, for classes and tokens). An image the template names by asset id (asset: "logo") needs the library to declare it (see Images); the report then has it through any import of the library.

Starting from one ​

  • A new report: the Page step of the catalog's New report wizard (new-report.md) lists the page templates of the libraries the environment has released, under each library's name and release, after Plain page. The report's first draft has a copy of the one you pick as its only page template, under its name in the library.
  • A report's page templates: in the designer's Pages tab, From a library… lists them too. Add as a new template adds a copy after the report's templates (letterhead, or letterhead-2 when the report has one already). Use for default puts a copy in place of the template that is open, keeping its id, so the sections and page breaks that use it follow. Either is one undo step. The nodes in the copy's bands get ids that the report doesn't use yet.

Drafts aren't offered: a report starts from what a library has released.

The presets reportd had before page templates (page sizes, layouts, styles and colours) became library items: the presets library and a style-<id> library per style. operations/migrations.md lists how each one maps.

Images ​

A library declares the images its components and page templates name by asset id in its manifest's assets, as a report declares its own (RFML §15), with each src a path among the library's files:

json
"assets": { "logo": { "kind": "image", "src": "images/logo.svg" } }

Every import of the library brings its declared assets to the report, so an imported component's { "type": "image", "asset": "logo" } is drawn from the library's file. The engine adds them to the report's own assets with each src as a library reference (library:brand/images/logo.svg), which reportd and the studio's canvas fetch from the release the import resolves to. The report's own asset of the same id wins, so a report can replace a library's logo. A release checks that each declared asset is an image or a font at a file the library has; declaring one is a minor change, removing one a major one, and pointing one at another file a patch.

Send to library… declares the images it copies, under the ids the component names them by. Make a local copy and Detach declare the images the copy names in the report (still as the library's files, library:<name>/<path>), so they stay drawn once the import no longer brings the component. The draft editor draws a library's components with its declared images.

A report can also name a library image directly, without a declaration in the library: { "kind": "image", "src": "library:brand/images/wave.svg" } in its own assets.

Fonts ​

A library declares the font families it offers: each family's files, with their weights and styles, the documents that came with it (its licence, readme and notes), and where it came from. Reports that import the library offer its families in their font pickers. Declared families are in the manifest's fonts, and their files under fonts/<family>/ (the family's name in lower case with hyphens: fonts/noto-sans/).

Adding fonts ​

Add fonts… on the Assets and fonts tab imports a family from a font source that is on. Sources are managed in Admin (operations/fonts.md); with none on, the tab says that someone with fonts.sources can turn one on.

  1. Pick a source, and search its families. Each family's name is drawn in its own font, fetched for the results shown, with its category.
  2. Pick a family. Its styles show as checkboxes, Regular picked. Below them: its licence, whether the files are static (one per style) or variable (one file covering the weights picked), their size, and the documents that come with it. From Fontsource, one subset is fetched (latin unless you pick another), since the engine reads one file per style. A licence that can't be told, or a font that forbids embedding, is said here, before Add.
  3. Add fetches the files and documents into the draft and declares the family.

A url source takes a link instead of a search: paste the link to a font file or a zip, and Read the link. reportd reads the family, weights and styles from the fonts themselves; a zip that holds several families lists them to pick one. A single font file's licence can be added by its own link.

A family the library has already, from the same source, is added to: the styles it has are kept (shown as "in the library"), and the new ones fetched. A family the library has from another source, or uploaded by hand, has to be removed first.

Declared families ​

Each family on the tab shows a line drawn in it, where it came from ("From Google Fonts @ 1a2b3c4", or "Uploaded by hand"), its styles, and its documents, which open as text (HTML as its source, so nothing in it runs). Remove takes the family, its files and its documents out of the draft.

Fonts uploaded by hand are listed under Other fonts until they are declared. Declare as a family… asks for the family's name and each file's weight and style, guessed from its name (BrandSans-BoldItalic.ttf is 700 italic; a variable file is 100 900).

Updates ​

Check for updates compares each imported family with its source now: GitHub's files at the source's ref, Fontsource's release, the index's version, or a link's ETag or hash. Only when you ask: nothing checks in the background. Each family says it is up to date, newer at the source (with what changed: files, documents, styles), gone from the source, or can't be checked (its source is off or removed).

Update fetches the same styles again into the draft, replacing the family's files and documents: a document the source added comes too, and one it no longer has goes. It ships when the library is released. The release notes say so ("Noto Sans updated from Google Fonts @ 1a2b3c4"), and it is a patch. Reports get the new font when they move to the new release, through Used by and its tests, where a change in the font's metrics would show.

Using a family in a report ​

In a report that imports the library, the Style panel's Font picker (the selected node's font-family) and the Theme panel's Default font (styles.defaults.document) list the families its libraries declare, by library, then the families the report's own font assets name, then the bundled fonts. Picking a declared family adds the font assets the report needs, one per file ({kind: "font", family, src: "library:<lib>/fonts/…", weight, style}), in the same undo step as the style, so the report renders with the font in every output. Taking a library's theme (the import's Theme box) or copying it into the report adds them too, for the declared families the theme names.

The studio's canvas lays text out in the report's font assets too: @report-forge/assets' AssetStore loads a bundle font from the draft, and a library: font from the release the report's import of that library resolves to (else the latest, as reportd resolves a draft's; ImportResolver.assetReleases), and hands the engine their bytes with the images. Where a font's bytes can't be had, text falls back to the bundled fonts.

Sending a report's component to a library ​

In a report's Library tab, Send to library… on one of the report's own components copies it into a library's draft (also on its menu, and as Then send it to a library when you extract a selection). The components it uses go with it, and so do the images from the report's bundle that it shows, to the same paths in the library, declared in its manifest under the same asset ids. If the draft already has a component with that name, the dialog says so before replacing it. The report keeps its own copy.

Once a release has the component, the report's Library tab offers Use billing's contact-card 1.3.0 instead. That removes the report's copy and imports the release, widening the import's range if it must, in one undo step.

A library's component in a report's component editor is shown, not edited. Edit in the library opens it in the library's draft.

Copy and detach ​

The other way round from sending a component to a library, a report can take a library's component or theme as its own, when it should stop following the library: to change it for this report only, or to keep it as it is whatever the library releases next. Each of these is one step to undo.

Make a local copy ​

In the Library tab's Imports, each import lists the components it brings. Make a local copy next to one (or in its right-click menu) copies the component's definition, as the release has it, into the report's own components, under the same name:

  • The component's instances stay as they are, and now use the report's copy. The page looks the same.
  • The import stops bringing that component. If the import named only that component and doesn't take the library's theme, the import goes. An import that brought all of the library's components names the others instead, so a component the library adds in a later release isn't imported until you add it.
  • The copy doesn't shadow anything, so there is no schema/component-shadowed warning.

The copy is then edited like any of the report's components: open it on its own, rename it, change its props or inline it. The library's later releases don't change it.

On a use node of an imported component, the inspector's Props section has Make a local copy too.

Detach ​

Detach makes the local copy, then inlines instances of it: each instance becomes a box with the instance's id, holding the component's nodes with its props filled in.

  • On a use node's props, Detach (copy and inline) inlines that instance. Other instances use the report's copy.
  • In the Library tab, Detach (and Detach (copy and inline) in the component's menu) inlines every instance in the report's own nodes.

The copy stays in In this document either way, so instances inside other components' templates keep working. Delete it there once it says "not used".

Copy the theme into this report ​

An import that takes the library's theme (its Theme box is ticked) has Copy the theme into this report, also in the import's right-click menu. It adds the theme's tokens, defaults and classes to the report's own styles, and stops taking the theme from the library: the Theme box is cleared, and an import that brought only the theme goes. The pages look as they did, because the report's own styles already win over the theme's:

  • Tokens and classes: the theme's are added where the report has none of the same name. A report's token or class replaces the theme's whole, so the theme's is left out.
  • Defaults: where only the theme has defaults for a type, they are added. Where both have them, they become one block: the report's declarations, and each of the theme's that the report doesn't override.

There are two cases where one block can't say what the two did, and the designer says so after the copy ("These may look different: …"):

  • The theme sets a shorthand, and the report's block sets some of its longhands through another shorthand. For example, the theme's border-top with the report's border-color: before, the report's colour won on the top edge too.
  • The theme sets a property in one part and the report sets it in another part of the same type. For example, the theme's header-cell colour with the report's cell colour: before, the report's colour won in header cells, since a later source wins whatever the part.

Check those places on the page, and set the value you want on the report's defaults.

Only the theme in use can be copied: the engine styles a report with the theme of its first import that takes one. If another import also takes its theme, it would be used once this one is copied, so the designer asks you to clear its Theme box first.

Commands ​

The commands are in the command palette (Ctrl+Shift+P), under Library, and in the Library tab's right-click menus where they apply:

CommandOn
Make a local copyA component an import brings
Detach (copy and inline)A component an import brings
Copy the theme into this reportAn import that takes its theme

Updating the reports that use a library ​

The Used by tab lists each report in the environment that uses the library. Reports in the trash are left out.

  • Published pins: the release the report's published version pins. Publishing a report records this in its lockfile, so the published version keeps rendering that release.
  • Draft range: the range the report's draft imports the library by, with the release that range picks now. The draft's preview uses that release.

To move reports to a release, choose them and a release (the latest is offered first), then press Update to 1.3.0 and republish…. Each report whose published version pins another release is chosen to begin with. A report that isn't published can't be chosen: publish it as usual.

The update runs as a dry run first. It changes nothing, and shows what would happen to each report:

  • Will be republished: its published version moves to the release. If the document's range doesn't include the release (an exact 1.0.0, say), the range widens to ^1.3.0, and that is the only change to the document. The tab also says how the draft follows.
  • Tests fail: won't be republished: the report's tests fail with the release, under tests.publishGate: block. The failing tests are listed. Under warn the report is republished, and the tests that failed are listed.
  • Conflict: the report has errors with the release, or a library report it embeds keeps another release. Its diagnostics are listed.
  • Nothing to update: it has no published version, it pins the release already, or it is frozen by the licence.

Republish publishes the reports that can be republished. Each one is a new published version, "Update billing to 1.3.0", made from the report's published version, not its draft. Its other libraries and data sources keep the versions they pinned. Then the report's draft follows: if its range doesn't include the release, the range moves to ^1.3.0 as one draft edit, recorded in the draft's edit log as a library update. The draft's other unpublished changes stay as they are, unpublished.

Other environments get the update when the reports are promoted, as always. Nothing updates by itself: a new release changes no published report until someone runs an update or republishes it.

Moving a library to the trash ​

Move to trash… on a library's page takes the library out of every environment at once, with its draft. It can be restored from the trash, so the dialog only says what happens, and the notice after it has an Undo.

While a library is in the trash:

  • It isn't listed: not on the Libraries page, in the Library tab's browser, or among the components a report can insert.
  • Nothing new can use it. A report that imports it, or uses its reports or images, says the library is in the trash: the Library tab's import shows it, and checking or publishing the draft gives the error ref/library-in-trash. The draft can't be published until the import goes or the library comes back.
  • Published reports keep working. A published version renders the releases its lockfile pins, from the trash as before. Promoting such a report still takes its pinned release along.
  • Its name stays taken: a new library can't have it until this one is restored or deleted for good.

Restore it from the catalog's Trash view, or the administration site's Trash page: the library comes back in every environment, draft and all.

Delete forever (trash.purge) is refused while a published report in any environment still pins one of the library's releases. The Trash view lists those reports, with the environment and the release each one pins. Update them to another library, or make local copies of what they use, and publish them; then delete it. Empty trash keeps such a library too, and the trash's sweep never deletes one, however long it has been there. Deleting it for good removes its releases from every environment branch; git history still holds them. See operations/trash.md.

Permissions ​

ToYou need
See librariestemplate.read
Create a library, change its draft, send a component to itlibrary.write
Release and promotelibrary.release (in the target environment, to promote)
Import, declare, update or remove font familieslibrary.write, and the source on
Turn font sources on and declare themfonts.sources (never agents)
Update the reports that use a librarytemplate.publish (agents only under agents.publish: allow)
Move a library to the trash, or restore itlibrary.write in every environment
Delete a library for goodtrash.purge

What you can't do is hidden, or says why.

How it works ​

  • reportd: libraries live on the environment branches at libraries/<name>/draft/ and libraries/<name>/<version>/ (internal/library). The routes are in internal/api/libraries.go: POST /v1/libraries creates one, PUT and DELETE /v1/libraries/{name}/draft/files/{path} write the draft, GET …/changes compares the draft with a release and suggests the version (internal/library/authoring.go), POST …/release takes notes, which go in the release's library.json, and GET …/releases lists each release with the commit that wrote it and its status in each environment. GET …/dependents lists the reports that use a library (internal/library/dependents: published pins from each lockfile, read once per branch tip, and the draft's references, as the engine inspects them). POST …/update {environment, version, templates, dryRun} republishes each report's published version with the library's pins moved (internal/api/library_update.go), running the publish pipeline with its tests gate, then moves the draft's range as a draft edit with the source library-update. Each report updated is audited as library.update. DELETE /v1/libraries/{name} moves a library to the trash (internal/library/trash.go, internal/api/library_trash.go): each branch gets a commit that removes the draft and adds libraries/<name>/trashed/item.json, the trash item. The catalogue then hides the library from everything but Pinned, which locked resolution reads, so published reports keep rendering; a draft's reference to it is ref/library-in-trash (internal/resolve). The trash routes restore and purge it with the other kinds; the purge asks the dependents index which published reports pin it (Pinning, which doesn't ask the engine).
  • The studio: the page is web/apps/designer/src/libraries/ (its tabs are listed in tabs.ts). useLibraryDraft.ts opens the draft in the engine, and @report-forge/components' library-draft.ts turns edits into draft files, sends components and uses a library's copy. The Used by tab is UsedByTab.vue. The command library.send is in commands/libraries.ts; the page is the Libraries workspace, opened by go.libraries (Alt+2, commands/go.ts) from anywhere.
  • Page templates: the manifest's pages (internal/library/pages.go, CheckPage). The engine's /v1/validate-library doesn't read them, and its WASM is near its size budget, so reportd checks each one at release with /v1/validate on a document of its own (validatePage in internal/api/libraries.go). In the studio, the draft document's page templates are the layout page (LAYOUT_PAGE) followed by the library's (draftPages), and draftWrites writes them back. PagesTab.vue edits them with PageTemplatePanel (library mode). The copies are addLibraryTemplate and replaceWithLibraryTemplate in @report-forge/pages (library.ts), which give the nodes ids with the engine's cloneForInsert. libraryTemplates.ts reads the released templates, and StartingTemplate.vue (the New report wizard's Page step) and LibraryTemplatePicker.vue (the page template panel) offer them. e2e: library-presets.spec.ts, with the stationery library in fixtures/libraries.json.
  • Copy and detach: localCopy and detachComponent in packages/components (commands.ts) build the batch. The definition comes from useLibrary.definition(), which reads the import's resolved release, so the engine doesn't change. Inline (inlineComponent, rfml-ops) runs on the copy in the same batch.
  • The theme copy: mergeTheme and copyTheme in packages/style (theme.ts). The merge follows rfml-style's cascade, with its shorthand table copied from crates/rfml-style/src/props.rs. Its tests (theme.test.ts) draw the page with the real engine before and after the copy and compare: the engine's explainStyle leaves imported themes out, so it can't be used for the "before".
  • The commands are library.copyLocal, library.detach and library.copyTheme (apps/designer/src/commands/detach.ts). A library item's menu subject offers them with copy, detach and copyTheme.
  • e2e: web/e2e/tests/library-detach.spec.ts, with billing's notice-banner and the brand theme from fixtures/libraries.json.
  • The library browser: components/library/LibraryBrowser.vue, shared by the Library tab (ComponentsPanel.vue, with the designer) and the Libraries page (LibrariesView.vue, without one). browse.ts reads an environment once: GET /v1/libraries, GET /v1/components, and each latest release's manifest, files and theme. It then reads each library's GET …/releases (notes and trees) and GET …/dependents (the usage count). It keeps them until the environment's ImportResolver is refreshed (by Refresh, a release or a promote). The items, the search and the filters are browser.ts in packages/components, with vitest. Thumbnails are thumbnails.ts and LibraryThumbnail.vue: previewComponent with theme: true on thumbnailDocument() (an 80 × 120 mm page), kept by a ThumbnailCache per release tree and component, one at a time. The browser loads with the Library tab and the Libraries page, outside the shell. e2e: web/e2e/tests/library-browser.spec.ts. The mock host's fixture releases can carry notes, other files and declared assets (billing's notes, and brand's images/wave.svg, declared as wave).
  • Images: the engine reads a manifest's assets (rfml-model library::imported, which effective merges into the document, and inspect_resolved lists for the host); internal/library checks and compares them (checkAssets, compareAssets). In the studio, ImportResolver.importedAssets mirrors the engine, and AssetStore.loadDocument loads the document's and the imports' images, from the bundle or a library release. e2e: web/e2e/tests/library-images.spec.ts.
  • Fonts: internal/fontinfo reads TTF, OTF, WOFF and WOFF2 (family, weight, style, axes, licence). internal/fonts holds the sources (source.go, presets and checks), the fetcher (fetch.go), each kind's catalogue (github.go with the METADATA.pb parser in metadata.go, url.go, fontsource.go, index.go), Declare and Replace (declare.go) and the update checks (update.go). The manifest's fonts and Check are internal/library/fonts.go, and EditDraft writes a draft in one commit. The routes are internal/api/font_sources.go, font_catalogue.go, font_import.go and font_updates.go. In the studio: libraries/AddFontsDialog.vue, DeclareFontsDialog.vue, FontDocumentDialog.vue and the reworked AssetsTab.vue; useLibraryDraft's change runs reportd's changes in the save queue and re-reads the manifest after, so a later save keeps them. The pickers are components/style/FontFamilyPicker.vue with declaredFonts.ts, and @report-forge/assets' fonts.ts builds the font assets, which its store.ts loads for the canvas. Admin's page is admin/FontSourcesPage.vue. e2e: web/e2e/tests/font-import.spec.ts, against mock-fonts.ts and fixtures/fonts.json.
  • The trash: "Move to trash…" is libraries/TrashLibraryDialog.vue; the Trash view (catalog/TrashView.vue) shows library items and a refused purge's reports (libraryUsesOf in the client). The import resolver asks whether a missing library is in the trash (inTrash). Moving a library to the trash or restoring it refreshes the environments' import resolvers, and the library browser with them; the Trash view, which loads with the shell, says so through src/librariesChanged.ts, which resolverFor checks. e2e: web/e2e/tests/library-trash.spec.ts.