Blocks Console

FoundationPreview
@wabbit/tome-blocks-consolev0.1.2

Shared presentational cores for console-style blocks, plus the console role names (surface, ink, line, accent hues, status) a skin maps to its own palette. Dependency substrate for block packs, not itself a sellable pack.

Install
  1. Add our registry to the .npmrc at the root of your project. Free packages install without a token.

    @wabbit:registry=https://npm.wabbit.com/
  2. Then install:

    npm install @wabbit/tome-blocks-console

Overview

@wabbit/tome-blocks-console

Shared render cores for console-style blocks (intercepted comms, transcripts, classification lines, log headers, redactions, alerts, waveforms, threat panels), plus the console role names that console skins colour themselves with. A core owns a block's markup and behaviour; a skin, meaning a block pack or a theme, owns every visual decision by passing the core its class names. Two packs that keep different sizes, spacing, palettes and typefaces can therefore render the same block through one core.

This package is a dependency substrate for block packs. It is not a sellable pack and registers no blocks.

Installation

pnpm add @wabbit/tome-blocks-console

The package is published to the Tome registry, so configure the @wabbit scope in your .npmrc first (see the repository README). A pack that renders the cores depends on this package; a site gets it through that pack.

| Peer | Range | Required | |---|---|---| | react | >=19.0.0 | yes |

Module surface

| Specifier | What it exports | |---|---| | @wabbit/tome-blocks-console | Everything below, re-exported from one entry | | @wabbit/tome-blocks-console/roles | The role names, the accent and status lists, and consoleVar | | @wabbit/tome-blocks-console/cores | The render cores, their prop and data types, and the cx class helper |

import { CONSOLE_ROLES, consoleVar } from '@wabbit/tome-blocks-console'
import type { ConsoleRole } from '@wabbit/tome-blocks-console/roles'

const role: ConsoleRole = 'surfaceRaised'
const name = CONSOLE_ROLES[role] // '--tome-console-surface-raised'
const style = { color: consoleVar('ink', 'currentColor') } // 'var(--tome-console-ink, currentColor)'

| Export | Kind | Purpose | |---|---|---| | CONSOLE_ROLES | const object | Every role name mapped to its --tome-console-* custom property | | ConsoleRole | type | A key of CONSOLE_ROLES | | CONSOLE_ACCENT_HUES, ConsoleAccentHue | const array, type | The seven accent hues | | CONSOLE_STATUSES, ConsoleStatus | const array, type | The five status names | | CONSOLE_VAR_PREFIX | const | The --tome-console- prefix | | consoleVar(role, fallback) | function | Builds var(--tome-console-<role>, <fallback>) |

Render cores

| Core | Base markup | Optional parts a skin can switch on | |---|---|---| | CommInterceptCore | <section>: <header> (frequency, strength), <ul> of one-line messages (time, callsign, content) | title row (indicator dot or badgeText badge, title), strengthPlacement (header, bar, footer, none) with barLabel, footer, scanlines, messageLayout: 'stacked' (callsign and time on one line above the content), hideContent (cipher glyphs, COMM_INTERCEPT_CIPHER, hidden from assistive technology), keepEmptyParts | | CommsTranscriptCore | <section>: <header> (label), <ol> of lines (time, callsign, message); a redacted line is an empty bar labelled "Redacted" | headerBadge, timestampPosition (start, end, none), callsignSuffix, quoteMessages, redactedBarRole: 'img', per-line color, keepEmptyParts | | ClassificationBannerCore | <div role="note"> with the text | none | | LogHeaderCore | <header>: optional prominent value, <dl> of labelled values; the labels and their order are data (fields) | prominent, bar, keepEmptyParts | | RedactedCore | INLINE <span>, otherwise <div>: badge (BLOCK_PANEL), visible caption, bar sized by barLength, image placeholder (IMAGE: icon and [CLASSIFICATION]) | role, ariaLabel, barLengthOn: 'root' | | SystemAlertCore | <div>: status dot, text; role="alert" only when urgent, otherwise role="note" | typeWord | | AmbientAudioCore | <div>: waveform (hidden from assistive technology), readable label | as: 'span', bars (heights and delays; default AMBIENT_AUDIO_DEFAULT_BARS) | | ThreatPanelCore | <section>: header (level badge, <h3> title), details, <ul> of indicators (dot, label, status word) | accentBar, levelDot, badgePosition: 'end', groupTitle, titleAs: 'span', indicatorsHeading (a headed box around the list) |

Every core takes classes, the skin's class names keyed by part (each core's props type lists its parts), plus variant (adds classes[variant] to the root), className and style. A CSS Modules or block-stylesheet class map whose class names equal the part names can be passed as it is; otherwise build the object. A part with no class renders without a class attribute. Cores read no rich-text adapter: ThreatPanelCore takes details already rendered, and renders no details element when it is undefined.

import { BlockStyles } from '@wabbit/tome-blocks-core/block-styles'
import { SystemAlertCore } from '@wabbit/tome-blocks-console'
import styles from './Alert.tome-css' // .root, .CRITICAL, .icon, .prefix, .text

export function Alert({ variant, text }: { variant: string; text: string }) {
  return (
    <>
      <BlockStyles sheet={styles} />
      <SystemAlertCore classes={styles} variant={variant} text={text} urgent={variant === 'CRITICAL'} typeWord={variant} />
    </>
  )
}

| Export | Kind | Purpose | |---|---|---| | CommInterceptCore, CommsTranscriptCore, ClassificationBannerCore, LogHeaderCore, RedactedCore, SystemAlertCore, AmbientAudioCore, ThreatPanelCore | server components | The render cores | | CommInterceptCoreProps, CommsTranscriptCoreProps, ClassificationBannerCoreProps, LogHeaderCoreProps, RedactedCoreProps, SystemAlertCoreProps, AmbientAudioCoreProps, ThreatPanelCoreProps | types | Each core's props, with its class parts listed in the doc comment | | CommInterceptMessageData, CommsTranscriptLineData, LogHeaderField, LogHeaderValueKey, LogHeaderValues, RedactedVariant, AmbientAudioBar, ThreatIndicatorData | types | The data shapes the cores take | | COMM_INTERCEPT_CIPHER, AMBIENT_AUDIO_DEFAULT_BARS | consts | Default cipher glyphs and waveform bars | | ConsoleClasses, cx(...names) | type, function | The class map type, and the helper that joins class names (undefined when none) |

Longform and signal cores

Fourteen more cores are shared by the signal theme and @wabbit/tome-longform, the long-form reading kit, for fifteen blocks (the aside core serves both the aside and the author aside). They follow the same contract (classes, variant, className, style) and add two things: rootData, data attributes for the root keyed without the data- prefix ({ 'block-variant': 'SINGLE' } writes data-block-variant="SINGLE"), and, where a block links somewhere, a link slot ('a' by default, or the platform's link component). Content each pack prepares its own way arrives as slots: rich text and pictures already rendered. No core resolves a block's width; the skin passes the class or style that places it.

| Core | Base markup | Optional parts a skin can switch on | |---|---|---| | AccordionCore (client) | <section> of <details> rows: <summary> (label, title, empty chevron), body. Closed bodies are in the page, collapsed | exclusive (one open at a time), signs (typed chevron text, closed and open), showLabels: false, disclosure: 'button' (button trigger with aria-expanded, closed body hidden), layout: 'terms' (button rows as a <dl>: <dt> question, <dd> answer), as: 'div' | | TabbedContentCore (client) | <section>: tab list (role="tablist", one role="tab" button each), the selected tab's role="tabpanel". Only the selected tab is in the tab order; arrow keys, Home and End move and select; the panel is named by its tab | keepPanel, as: 'div' | | SpoilerCore (client) | <section>: label, wrapper holding the content and, until revealed, a button over it (visible words in an inner span) | revealName (spoken name, keeping the visible words), labelAs: 'div', contentHidden and contentRevealed class parts, as: 'div' | | AnchorSectionCore | <section id>: <h2>, subtitle <p> | targetOn: 'heading', headingAs, as: 'div' | | AsideCore | <aside>: label <span>, content | labelAs: 'div', as | | CalloutCore | <section>: label <span>, content | labelRow (the label in its own row), as: 'aside' | | ChapterDividerCore | form: 'rules' (rule, glyph, rule) or 'glyph', both role="separator"; 'numbered' (badge and an <h2> title, no separator role) | body (contents wrapped), ruleAs: 'div', numberedRules (rules either side of badge and title), titleAs | | CrossLinkCore | link > body (heading row: title, badge; description <p>), metrics strip | layout: 'stacked' (badge row, title, description, metrics, all <div>s), badgeStyle, link | | DataTableCore | <section>: wrapper around the <table> (column headers scope="col"), caption <p> under it naming the table (aria-labelledby) | wrapTable: false, keepEmptyParts, as: 'div' | | EpigraphCore | <blockquote>: text <p>, <footer> (<cite> attribution, source <span>) | textAs: 'pre' (verse keeps its spaces) | | FootnotesCore | <section>: <ol> of notes (id="fn-<number>", list value, <sup> number, text) | grouped (category <h4> and list per group, fallbackCategory), numberAs: 'span', numberSuffix, setValue: false, bareList (the <ol> is the root) | | ImageGridCore | <section>: grid of <figure>s, group caption <p>. A picture is a slot (image, rendered as given) or a media record (media, drawn as <img> with altText, else the record's alt) | imageFrame | | KeyFactsCore | <section>: <h3> title, <dl> of facts; a status adds status-<status> to the value | statusWords (a visually hidden word per status, in a statusWord part), titleAs, listAs: 'div' \| 'table', valueFirst, statusAttr (data-status per fact), keepEmptyParts | | SeriesNavCore | <nav aria-label>: title <span>, <ol> of parts; the current part is text marked aria-current="page" | titleRow, showParts: false, adjacent (previous and next links with arrows), keepEmptyParts, link |

| Export | Kind | Purpose | |---|---|---| | AnchorSectionCore, AsideCore, CalloutCore, ChapterDividerCore, CrossLinkCore, DataTableCore, EpigraphCore, FootnotesCore, ImageGridCore, KeyFactsCore, SeriesNavCore | server components | The server cores | | AccordionCore, TabbedContentCore, SpoilerCore | client components | The interactive cores | | AccordionCoreProps, AnchorSectionCoreProps, AsideCoreProps, CalloutCoreProps, ChapterDividerCoreProps, CrossLinkCoreProps, DataTableCoreProps, EpigraphCoreProps, FootnotesCoreProps, ImageGridCoreProps, KeyFactsCoreProps, SeriesNavCoreProps, SpoilerCoreProps, TabbedContentCoreProps | types | Each core's props, with its class parts listed in the doc comment | | AccordionItemData, ChapterDividerForm, CrossLinkMetricData, DataTableCellData, DataTableRowData, FootnoteData, ImageGridItemData, ImageGridMediaData, KeyFactData, SeriesPartData, TabData | types | The data shapes the cores take | | CoreDataAttributes, CoreLinkComponent, dataAttributes(data) | types, function | The root data-attribute map, the link slot type, and the helper that writes the attributes |

Readout-family cores

Nine more cores serve data panels: asset cards, personnel rosters, metric grids, objective lists, phase markers, progress bars, stat strips, map legends and sensor readouts. They are exported from both entries above, next to the cores in the previous section, and follow the same contract (classes, variant, className, style), plus data, data attributes for the root keyed without the data- prefix ({ variant: 'GRID' } writes data-variant="GRID"). Optional parts are off by default.

| Core | Base markup | Optional parts a skin can switch on | |---|---|---| | AssetCardCore | <section> of one panel per asset (comparison adds a second): image wrapper (only when image is not undefined; the image arrives rendered), details of an <h3> name, maker, kind and a <dl> of specs; a divider between two panels | divider: false, manufacturerPosition: 'before-name' | | PersonnelCardCore | <section> of cards: avatar box (a blank placeholder when avatar is undefined), info column of name, handle, role and a status badge word | statusMarker: 'dot' (a dot hidden from assistive technology, then a statusWord), statusPosition (info, start, end), sublineSeparator (handle and role as one line), textAs: 'p', per-person style | | MetricGridCore | <section> with a grid of cards (only when there are metrics): value, label, sublabel, delta | accentBar, labelPosition: 'before-value', per-metric accentColor class and style | | ObjectiveListCore | <section>: heading, <ul> of items (glyph hidden from assistive technology, status word, text, note); grouped puts a heading one level down and a <ul> per phase, objectives with no phase under unphasedLabel ("Unassigned") | unphased: 'after' (an unheaded list after the groups), glyphs (default OBJECTIVE_STATUS_GLYPHS), headingLevel (default 3) | | PhaseMarkerCore | <div>: number badge and title, each only when set | hideTitle, rules (hairlines either side, hidden from assistive technology), groupContent, frameBadge, frameTitle | | ProgressBarCore | <section>: rows of a meta line (label, percent) over a track and fill clamped to 0 to 100; segmented, a labels line above one track of segments at their own values | segmentWidth: 'share' (share of the total), legend: 'entries' (dot, label, value), legendPosition: 'after', segmentTitles | | StatStripCore | <section> with a strip of stats (only when there are stats): value, label, delta | deltaPosition: 'before-label', per-stat color class | | MapLegendCore | <section>: heading, <ul> of entries (symbol, label) | dotFallback (a dot when an entry has no symbol), headingLevel | | SensorReadoutCore | <section>: heading, then view: table (a labelled, keyboard-focusable scroll region round a five-column table, default columns SENSOR_READOUT_COLUMNS), cards, or rows (name and type, a tone-<tone> class per row) | titleBar (a header bar with a decorative pulse, kept with no title), columns, statusMarker: 'dot', emptyCell, regionLabel, headingLevel |

A delta in the metric grid and the stat strip carries delta-up, delta-down or delta-flat by its leading sign or arrow (deltaDirection), so a skin can colour it or leave it alone. Colours and statuses arrive as class names keyed by value (card-accent-<accent>, status-<status>, bar-<color>, color-<color>), so a skin styles the values it knows and an unknown value falls back to the base part's look.

| Export | Kind | Purpose | |---|---|---| | AssetCardCore, PersonnelCardCore, MetricGridCore, ObjectiveListCore, PhaseMarkerCore, ProgressBarCore, StatStripCore, MapLegendCore, SensorReadoutCore | server components | The readout-family cores | | AssetCardCoreProps, PersonnelCardCoreProps, MetricGridCoreProps, ObjectiveListCoreProps, PhaseMarkerCoreProps, ProgressBarCoreProps, StatStripCoreProps, MapLegendCoreProps, SensorReadoutCoreProps | types | Each core's props, with its class parts listed in the doc comment | | AssetCardData, AssetSpecData, PersonnelData, MetricData, ObjectiveData, ProgressBarData, StatData, MapLegendEntryData, SensorContactData | types | The data shapes the cores take | | OBJECTIVE_STATUS_GLYPHS, SENSOR_READOUT_COLUMNS | consts | Default objective glyphs and sensor table column headings | | dataAttributes, CoreDataAttributes | function, type | Turns the data prop into data-* attributes | | headingTag, CoreHeadingLevel | function, type | The heading element for a level, moved down for nested headings | | deltaDirection, DeltaDirection | function, type | Reads a delta's direction from its sign |

Server and client posture

Server-safe apart from three cores. AccordionCore, TabbedContentCore and SpoilerCore carry 'use client': they hold open, selected or revealed state, and take their content already rendered, so a server skin can hand them server-rendered rich text. Every other core is a server component with no state, effects or handlers, so it renders in a Server Component tree and inside client components alike. Nothing reads environment variables. The roles module is plain data and a pure string function, safe in Payload config files and Node scripts. The cores ship no stylesheet of their own: the skin's stylesheet is the only CSS a core's markup receives.

Console roles

A site or theme defines these custom properties on a scope that contains the block (a theme root, a section wrapper or :root). A skin's stylesheet reads them with a fallback, so an undefined role degrades to the skin's own value rather than to nothing.

| Role | Custom property | Meaning | |---|---|---| | surface | --tome-console-surface | The panel background | | surfaceRaised | --tome-console-surface-raised | A step above the surface: headers, hovered rows | | line | --tome-console-line | Hairlines and borders | | ink | --tome-console-ink | Primary text | | inkMuted | --tome-console-ink-muted | Secondary text | | inkFaint | --tome-console-ink-faint | Tertiary text, captions and idle labels | | accentRed | --tome-console-accent-red-on-surface | Red as text on the surface | | accentBlue | --tome-console-accent-blue-on-surface | Blue as text on the surface | | accentGold | --tome-console-accent-gold-on-surface | Gold as text on the surface | | accentCyan | --tome-console-accent-cyan-on-surface | Cyan as text on the surface | | accentAmber | --tome-console-accent-amber-on-surface | Amber as text on the surface | | accentGreen | --tome-console-accent-green-on-surface | Green as text on the surface | | accentGhost | --tome-console-accent-ghost-on-surface | A low-emphasis neutral accent as text on the surface | | statusOk | --tome-console-status-ok | Healthy or complete | | statusWarn | --tome-console-status-warn | Needs attention | | statusFail | --tome-console-status-fail | Failed or blocked | | statusInfo | --tome-console-status-info | Informational | | statusIdle | --tome-console-status-idle | Inactive or not started |

The accent roles name text colours that must be readable against --tome-console-surface. Whoever supplies an accent is responsible for its contrast on that surface.

Writing a skin

A skin is a component in a block pack that maps its own block data onto a core's props (stored field names and values never change), picks which optional parts are on, renders its stylesheet and passes its class names as classes. Keep colours in the skin's stylesheet, read through the console roles above (var(--tome-console-*, fallback)) or the pack's own tokens, so a site's console palette reaches the block. Per-block stylesheets in a block pack follow the pack convention (*.tome-css rendered by <BlockStyles> from @wabbit/tome-blocks-core).

License

Commercial. See LICENSE.md.

Exports

  • @wabbit/tome-blocks-console
  • @wabbit/tome-blocks-console/roles
  • @wabbit/tome-blocks-console/cores

Changelog

v0.1.2patch

775f90a: Published packages now contain compiled JavaScript and type declarations under a one-line licence banner, and no longer include source maps. What you install: one compiled `.js` (ESM) and `.cjs` (CommonJS) file per source module, its `.d.ts` / `.d.cts` declarations, and the stylesheets, fonts and other assets a package already shipped. Every JavaScript module opens with a comment naming the package and its licence: `/*! @wabbit/<package> — © Wabbit, LLC. Wabbit Tome Commercial License (see LICENSE.md). Not for redistribution. */`. The `.map` files and the `sourceMappingURL` comments that pointed at them are gone, which roughly halves the size of each tarball. Debugging: the code is still unbundled and unminified, one readable file per module, so a stack trace points at real code with real names. Line numbers in a stack trace are one higher than before, because of the banner line. A `'use client'` directive stays the first statement of its module (the banner is a comment above it), so React Server Component boundaries are unchanged. No API change, no runtime behaviour change, and nothing to do on upgrade. In `@wabbit/tome-blocks-gallery`, the source snapshots `extractGallerySource` writes from an installed pack leave out the licence banner line, so a component or config snapshot starts at the code and a paid block's preview shows its first 15 lines of real code.

  • 775f90a: Published packages now contain compiled JavaScript and type declarations under a one-line licence banner, and no longer include source maps. What you install: one compiled `.js` (ESM) and `.cjs` (CommonJS) file per source module, its `.d.ts` / `.d.cts` declarations, and the stylesheets, fonts and other assets a package already shipped. Every JavaScript module opens with a comment naming the package and its licence: `/*! @wabbit/<package> — © Wabbit, LLC. Wabbit Tome Commercial License (see LICENSE.md). Not for redistribution. */`. The `.map` files and the `sourceMappingURL` comments that pointed at them are gone, which roughly halves the size of each tarball. Debugging: the code is still unbundled and unminified, one readable file per module, so a stack trace points at real code with real names. Line numbers in a stack trace are one higher than before, because of the banner line. A `'use client'` directive stays the first statement of its module (the banner is a comment above it), so React Server Component boundaries are unchanged. No API change, no runtime behaviour change, and nothing to do on upgrade. In `@wabbit/tome-blocks-gallery`, the source snapshots `extractGallerySource` writes from an installed pack leave out the licence banner line, so a component or config snapshot starts at the code and a paid block's preview shows its first 15 lines of real code.
v0.1.1patch

bd092c2: Adds fourteen shared render cores for editorial blocks, such as accordion, tabs and data table, on the `./cores` subpath. The cores cover the accordion, anchor section, aside, callout, chapter divider, cross link, data table, epigraph, footnotes, image grid, key facts, series nav, spoiler and tabbed content. The signal theme and `@wabbit/tome-longform` render their matching blocks through them; the aside core serves both the aside and the author aside. They keep the existing contract (`classes` keyed by part, `variant`, `className`, `style`, optional parts off by default) and add `rootData`, data attributes written on the root, and a `link` slot where a block links somewhere. Rich text and pictures arrive already rendered, and no core resolves a block's width. Three cores are client components because they hold state: `AccordionCore` (open rows; closed bodies stay in the page, collapsed), `TabbedContentCore` (the selected tab; arrow keys, Home and End move between tabs and each panel is named by its tab) and `SpoilerCore` (revealed). The others are server components. The cores ship no stylesheet. New exports: the fourteen cores, their prop and data types, `CoreDataAttributes`, `CoreLinkComponent` and `dataAttributes`.

  • bd092c2: Adds fourteen shared render cores for editorial blocks, such as accordion, tabs and data table, on the `./cores` subpath. The cores cover the accordion, anchor section, aside, callout, chapter divider, cross link, data table, epigraph, footnotes, image grid, key facts, series nav, spoiler and tabbed content. The signal theme and `@wabbit/tome-longform` render their matching blocks through them; the aside core serves both the aside and the author aside. They keep the existing contract (`classes` keyed by part, `variant`, `className`, `style`, optional parts off by default) and add `rootData`, data attributes written on the root, and a `link` slot where a block links somewhere. Rich text and pictures arrive already rendered, and no core resolves a block's width. Three cores are client components because they hold state: `AccordionCore` (open rows; closed bodies stay in the page, collapsed), `TabbedContentCore` (the selected tab; arrow keys, Home and End move between tabs and each panel is named by its tab) and `SpoilerCore` (revealed). The others are server components. The cores ship no stylesheet. New exports: the fourteen cores, their prop and data types, `CoreDataAttributes`, `CoreLinkComponent` and `dataAttributes`.
  • 19a540c: Adds nine shared render cores for data panels (asset card, personnel card, metric grid, objective list, phase marker, progress bar, stat strip, map legend, sensor readout), exported from the root and `./cores` entries beside the existing cores, plus three helpers they use: `dataAttributes`, `headingTag` and `deltaDirection`. Each core is a server component that owns a block's markup and behaviour and takes every class name from the skin that renders it, so two packs with different sizes, spacing and palettes render the same block through one core. Optional parts are off by default and switched on by props: among them the maker above the asset name, a status dot instead of a badge, a tile accent bar, objectives with no phase after the groups instead of under "Unassigned", hairlines and a framed number on the phase marker, segments sized by their share of the total with a legend after the track, a dot for a legend entry with no symbol, and a sensor header bar. Titles are headings whose level is an option (`headingLevel`, default 3), lists are lists, and the sensor table scrolls inside a labelled region a keyboard can reach. Every core also takes `data`, data attributes for its root. The cores ship no stylesheet.
  • 67cdd90: Adds eight shared render cores (comm intercept, comms transcript, classification banner, log header, redacted, system alert, ambient audio, threat panel) on a new `./cores` subpath. Each core is a server component that owns a block's markup and behaviour and takes every class name from the skin that renders it (`classes`, keyed by part), so two packs with different sizes, spacing and palettes can render the same block through one core. Optional parts (a title row, a footer, stacked messages, hidden message text, a headed indicator box and others) are off by default and switched on by props. The cores ship no stylesheet. The package now declares `react` (`>=19.0.0`) as a peer, which every site that renders blocks already has.
v0.1.0minor

d99c698: New package: the console role names and a `consoleVar` helper that console-style block cores and the skins that colour them will share. The package ships no blocks or render cores yet. It is a dependency substrate for block packs and has no peer dependencies.

  • d99c698: New package: the console role names and a `consoleVar` helper that console-style block cores and the skins that colour them will share. The package ships no blocks or render cores yet. It is a dependency substrate for block packs and has no peer dependencies.