Skip to content

VCST-5644: Interactive Virto Atomic Periodic table - #3096

Open
OlegoO wants to merge 53 commits into
devfrom
docs/VCST-5644-atomic-architecture-map
Open

VCST-5644: Interactive Virto Atomic Periodic table#3096
OlegoO wants to merge 53 commits into
devfrom
docs/VCST-5644-atomic-architecture-map

Conversation

@OlegoO

@OlegoO OlegoO commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Description

Interactive Virto Atomic Periodic table

image

References

QA-test:

Jira-link:

Artifact URL:


Note

Low Risk
Documentation and static client-side assets only; no changes to platform runtime, auth, or data paths.

Overview
Introduces the Atomic Architecture Map — a one-screen, double-clickable index.html poster (no build/npm) that layers architecture bands, an atoms periodic table, molecules, and cells, with search, adoption filters, hash deep links, and a details drawer (including full-screen mode and print).

app.js renders everything from content/*.js without dependencies: DOM via el() (not innerHTML), lightweight C#/JSON/XML highlighting, doc links derived from vc-docs page paths, GitHub links for api[].file, and multiple diagram kinds (lanes, pipeline, topology, stack, tree, section, flow) with SVG topology edge routing.

content/architecture.js (and the README) document how to author atoms and diagrams; check-content.js validates schema, cross-references, repo paths, diagram structure, registry-backed molecules/cells, and optional --online vc-docs page checks.

The map is meant to publish with MkDocs via awesome-pages; maintainers edit data in content/atoms.js and run node docs/atomic-map/check-content.js to keep references honest.

Reviewed by Cursor Bugbot for commit da9c507. Bugbot is set up for automated code reviews on this repo. Configure here.

OlegoO and others added 30 commits July 30, 2026 18:13
Approved design for docs/atomic-map/ — a one-screen interactive poster
of Virto Commerce and .NET 10 primitives ("atoms") under a solution
architecture band, with a reserved molecules shelf.

Grounded in a review of src/: job atoms are engine-agnostic and in
flight (Platform.Hangfire is no longer in the solution), Redis caching
is an invalidation bus rather than a shared store, and HybridCache /
Keyed DI / Channels / TimeProvider / Minimal APIs have no call sites —
hence the per-atom adoption badge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A one-screen interactive poster of the building blocks a Virto developer
works with: 53 atoms across 8 families under a 6-layer solution
architecture band, plus 14 reserved molecule tiles. Click any tile for
pattern, when-to-use, what-to-avoid, real API names with file paths, and
a C# snippet taken from this repository.

No build step: an HTML shell plus vanilla CSS/JS and content data files.
Content loads via classic <script src> rather than ES modules, because
modules are CORS-blocked on the file:// origin and would break opening
the page by double-click. Zero external requests at runtime.

Every atom carries an adoption badge (platform / module / available /
in-flight / legacy), which is where most of the value sits. Grounding the
content in src/ corrected several things a from-memory map would have got
wrong:

- Platform.Hangfire is no longer in the solution; jobs now sit behind
  engine-agnostic IBackgroundJob contracts, so job atoms are in-flight
- Redis caching is an invalidation bus, not a shared cache store
- HybridCache, Keyed DI, Channels, TimeProvider and Minimal APIs have no
  call sites, so each is marked available with the platform alternative
- ICancellationToken is an obsolete Hangfire shim (VC0014)
- MVC serializes with Newtonsoft, not System.Text.Json

check-content.js verifies every api[].file and docs[].href resolves and
every atom is complete; it caught content that cited AdminUIAccess* types
existing only on an unmerged branch. Each atom records the platform
version it was verified against so staleness stays visible.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…full-screen mode

The list items in the drawer were flex containers, which made every inline
<code> child a separately shrinkable flex item. Combined with
overflow-wrap: anywhere, short identifiers were split mid-word —
"IBackgr / oundJob", "Task / .Run" — and the tall boxes threw the line
off. Items are now blocks with an absolutely positioned marker, and code
uses break-word so an identifier splits only when it genuinely cannot fit.
Verified: every inline code chip renders on one line, down to a 375px
viewport.

Adds an expand toggle (⤢ button, or "f") that takes the panel full screen
and reflows sections into columns, capped at 1360px so prose lines stay
readable. Snippets, lead text and notes span every column; only the short
lists column up. The preference persists across atoms and reloads, sets
aria-modal while expanded, and the button is hidden below 720px where the
panel is already full width.

Also widens the collapsed panel 496→540px, and reveals the snippet copy
button on hover rather than showing it at full strength always.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lighting

Fixes a regression from the previous commit: `white-space: normal` was
added to the bare `code` selector to help inline chips wrap, but it also
applied to <code> inside <pre>, overriding line preservation — every
snippet collapsed into one wrapped paragraph. The inline rules are now
scoped `:not(pre) > code`, and `.snippet pre code` resets the chrome it
was inheriting (it had been drawing a second border inside the frame).

Snippets now get a thin header bar carrying the language label — the
`lang` field was in the content all along and unused — with the copy
button beside the code rather than floating over its first line.

Adds a small tokenizer for csharp / json / bash: comments, strings,
numbers, keywords and types, five colours, no library. Tokens are built as
spans with textContent, so highlighting cannot corrupt code or inject
markup. Verified across all 47 snippets that rendered text is
byte-identical to source and line counts match; interpolated strings,
regex literals in strings, JSON keys vs values, and bare URLs in shell
commands all tokenize correctly. All five colours clear WCAG AA 4.5:1 in
both light and dark (worst 4.79), and print falls back to weight and
italics since the colours do not survive greyscale.

Lines scroll rather than wrap — wrapping destroys the indentation that
makes C# readable. The longest snippet line is 105 chars, which overflows
the 540px docked panel but fits the full-screen panel with room to spare.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…gfire atom

Fire & forget was badged legacy, which conflated the pattern with the
static facade that implements one variant of it. Enqueuing work and not
waiting for the result is a legitimate, recommended thing to do — it is now
● PLATFORM, framed as "await the enqueue, not the work", with Task.Run and
un-awaited Tasks as the anti-patterns in Avoid and the static facade
demoted to a gotcha.

Adds a dedicated Hangfire atom (✕ LEGACY) covering what is actually the
legacy path: code that names Hangfire.* types, and the dead
VirtoCommerce.Platform.Hangfire package, now a type-forwarding shim. The
badge explicitly does not mean "do not run Hangfire" — it remains the
default engine — and the note says so, because the distinction is the whole
point.

Content grounded in the vc-module-background-jobs README rather than
inferred: module id VirtoCommerce.BackgroundJobs, engines Hangfire /
RabbitMQ / InMemory selected by VirtoCommerce:BackgroundJobs:Provider,
Mode = Producer|Worker|Both, EnableLegacyHangfire defaulting to true, and
the two-step migration. Job atoms now name the module and link the repo,
and the background-processing-hub molecule is updated to match.

Also fixes markdown emphasis, which had never rendered: nine **bold**
phrases across atoms.js and architecture.js — including Uses / Extends /
Reacts to in the Modules layer — showed as literal asterisks. rich() now
supports **bold**, with bold as the OUTER split so **`Code`** works;
splitting backticks first separates the markers and silently bolds
everything to the next one, which is exactly what it did on the first
attempt. Note labels moved to their own class so emphasis in a note body
no longer inherits the label's amber.

Verified across all 74 drawers: no literal ** remains, no bold run runs
long, and **`VirtoCommerce.BackgroundJobs`** renders as strong>code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ont note

Channels was a flat bullet list, which hid the thing that actually matters
about it: which protocol each consumer speaks. It now renders a layered
diagram — sales channels, back office & operations, system-to-system —
with a protocol chip per node, converging on the platform.

Adds a declarative `schema` structure to architecture.js and a renderer for
it, so any layer can gain a diagram from content alone. `viaKind` colours
the chip (graphql / rest / trend / plain) and `trend: true` draws the node
dashed, reusing the "not shipped yet" language of the reserved molecule
tiles. Groups lay out side by side in the expanded panel.

Content, grounded rather than guessed:
- Back office is the three hosts this repo documents — Admin UI (Commerce
  Manager, AngularJS blades), Vendor Portal (VC-Shell, Vue 3 + Module
  Federation, used by Marketplace), and custom SPAs via the <apps>
  mechanism — all REST, all now converging on
  GET /api/apps/{appId}/manifest.
- AI agents are marked as a trend, not a feature: MCP exposes capabilities
  as tools and UCP (Universal Commerce Protocol, Google/Shopify, announced
  NRF Jan 2026) is the commerce conversation above it, with UCP
  capabilities mapping 1:1 to MCP tools. A gotcha states plainly that the
  platform implements neither today.
- Integration middleware and outbound webhooks split out as
  system-to-system.

Removes the legacy vc-storefront bullet as requested.

check-content.js now validates schema shape: group titles, node names,
known viaKind values, a viaKind without a via label, and a missing target.
All protocol chips and schema text clear WCAG AA in both themes (worst
4.54). Poster still fits one screen despite the fifth layer tag.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… in the middle

Restructures the schema from "groups then a target at the bottom" into an
ordered list of rows, so the target can sit anywhere in the stack. Channels
now reads top to bottom: sales channels, ↓, Virto Commerce Platform, ↑,
Admin UI & back office, ↕, Integrations.

Rows are joined by connector pills carrying the protocol and a direction
glyph, drawn on a vertical line. The direction is the point: everything
points at the platform, because nothing else holds business logic — sales
channels call down into it, back office and integrations call up.

The stack stays a single column at every width; extra room in the expanded
panel goes into wider nodes inside each row rather than reflowing the rows,
since the vertical stack IS the diagram.

check-content.js validates the new shape: known connectorDir values, a
connectorDir without a label, rows that are neither a target nor a titled
group, and exactly one target row per schema.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tocommerce.org

Two fixes.

Protocol chips in the Channels schema sat at different heights because each
one followed the end of its card's description. They are now pinned with
margin-top:auto, and since grid rows stretch cards to equal height they land
on one baseline per row — verified per visual row, both docked (3+2 wrapped)
and full screen (5 across). The trend chip loses its dashed border so it
stops competing with the dashed card that already marks it, and chips gain a
subtle fill so they read as badges rather than outlines. The AI-agents
description is trimmed to two lines, since it alone was driving the row
height; the full MCP/UCP explanation moves into the bullets where there is
room for it.

Documentation links now resolve to the public docs site. They pointed at
repo-relative .md paths, which worked in an editor but were broken on the
published site, where pages live at /path/ rather than /path.md. Content
stores the vc-docs page path and the renderer derives the URL, so the base
and the version segment sit in one constant; off-site links open in a new
tab.

Crucially the docs site is built from vc-docs, NOT this repo's docs/ folder,
so the paths are not interchangeable — 92 links were remapped to real
vc-docs pages read from that repo, 3 dropped for having no published
equivalent (autorest, B2B-multiregional) rather than pointed somewhere
misleading, and 6 in-repo design-spec links kept relative.

check-content.js gains --online, which pulls the vc-docs file list via gh
and fails on any page that is not a real file. All 34 distinct pages verified
against 915 real vc-docs pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The map covered the platform well but under-represented what every real
solution actually installs — it had exactly one ◐ MODULE tile. Adds four,
each grounded in the module's own source rather than inferred:

- Assets & blob storage (As) — VirtoCommerce.Assets with AzureBlobAssets or
  FileSystemAssets providers. IBlobStorageProvider / IBlobUrlResolver member
  names read from vc-module-assets. This closes a gap the file-operations
  atom already pointed at: there is no blob abstraction in the platform, so
  without this module there is no file API beyond System.IO.
- Indexed search (Se) — VirtoCommerce.Search with an engine module per
  deployment (ElasticSearch8/9, OpenSearch, Lucene, AzureSearch, Algolia,
  Elastic App Search). ISearchProvider / IIndexingManager /
  IIndexDocumentBuilder signatures read from vc-module-search, including the
  VC0014-obsolete IndexDocumentsAsync overloads.
- Seq & log sinks (Sq) — VirtoCommerce.SeqLog. Verified it ships no
  interfaces: it is configuration, and you keep logging through ILogger<T>.
- vc-build CLI (Cb) — VirtoCommerce.GlobalTool over nuke.build, with its real
  target names and vc-package.json as the reproducibility contract.

Module ids, repos and versions come from the vc-modules registry
(modules_v3.json), so the names are what a developer will actually type.

The ◐ badge blurb generalises from "installable module" to "ships outside
platform core — install the module or tool", so the CLI fits it honestly
rather than being filed under a label that misdescribes it.

Side effect worth noting: the Infrastructure layer was previously empty, so
its hover spotlight and atom list did nothing. It now holds Assets, Search
and Seq.

Infra & ops grew to 12 tiles, which added a sixth tile row and pushed the
poster 43px past one screen. Recovered by compacting the molecules shelf —
14 reserved placeholders should not outweigh real atoms, so their subtitle
clamps to one line on the poster and stays full in the drawer — plus small
gap and heading trims. Back to exactly 900px at 1440x900 with no horizontal
overflow, tile and molecule names unclipped.

All 43 linked vc-docs pages verified with --online.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The expanded panel used an auto-fit grid, so every block got an equal
column: Gotchas wrapped into a narrow ribbon while Docs sat in mostly
white space, and the two short pill blocks each burned a full column.

The grid is now a fixed 4-track layout above 1120px, and block() takes an
explicit span. Gotchas claims 2 tracks (638px of 1360), Docs 1, and See
also + Part of are wrapped in one d-col element so they stack in the
remaining track instead of taking one each. Below the breakpoint it falls
back to 2 equal columns with no span, and the docked panel is unchanged
block flow.

The pill column is only created when at least one of the two blocks has
content, so an atom with no seeAlso and no molecule cannot leave an empty
track. Layer drawers get the same Gotchas treatment for consistency.

Swept all 58 atom and 6 layer drawers expanded: Gotchas is ~2x Docs
everywhere both appear, they share one row, no empty pill columns, and no
horizontal overflow at 1440, 1000 or docked width.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…, restyle headers

Three things.

**A solution layer.** The band explained the product's layers but never what a
custom Virto project actually consists of. A new first layer does, as a
vertical schema: what you own (custom modules, storefront, back-office app,
middleware) → what you consume (platform, commerce/provider/XAPI modules) →
vc-package.json + container image as the focal point → environments. The
composition file is the target because a solution IS its pinned set of
versions. Bullets cover the ratio (mostly configuration over a handful of
custom modules), the extend-in-this-order rule, and the storefront being a
separate deliverable; gotchas cover never forking vendor source, caret ranges
drifting between machines, prefixing custom properties, and per-provider
migrations.

**Layer drawers wasted space.** "What lives here" sat in one narrow track with
three empty beside it — the void in the screenshot. It now spans full width
with the list flowing into readable columns. The matrix stops borrowing
.api-list styling, which was monospace and made prose read like file paths;
it has its own class and lays out side by side when there is room. Layers
owning no atoms (Channels, Your solution) had a 69% bottom row, so Docs takes
the slack when there is no atoms block. Every layer now fills 93-97% per row.

**Headers and the lead.** Title is larger and heavier, more so when expanded,
with a firmer rule under the head. The lead paragraph left half the width
empty on a wide screen, so tags — previously visible only on the poster tile —
now render as chips beside it ("Key pieces" for layers, "Also known as" for
atoms), and the lead claims half a row only when something sits next to it.
The d-col wrapper carries its own margin so its box aligns with sibling
blocks rather than starting 16px above them.

Seven band columns made layer tags wrap to three rows, pushing the poster
14px over one screen; smaller tag chips plus trimming two over-long tag lists
brings it back to exactly 900px at 1440x900, one band row, no overflow.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… IModule resolution

Adds a module.manifest atom and improves both atoms that touch it, because the
two questions asked belong in different places.

**How the manifest actually loads an IModule** turns out not to work the way
the element names suggest, so it is now stated explicitly. The platform scans
the assembly named by `assemblyFile` for non-abstract IModule
implementations. With exactly one it uses that and ignores `moduleType`
entirely — which is why a stale `moduleType` can sit in a manifest for years
without breaking anything. Only with several does it disambiguate, matching by
assembly-qualified-name prefix, and no match throws ModuleInitializeException.
Zero implementations in a named assembly throws too. The class is created with
Activator.CreateInstance, so it needs a public parameterless constructor —
which is the reason the IHasConfiguration / IHasLogger / IHasHostEnvironment
marker interfaces exist at all.

**Frontend-only modules are a first-class shape, not a degenerate one.** Omit
`assemblyFile` and the catalog marks the module Initialized on the spot and
never loads an assembly — the source comment says "modules without assembly
file don't need initialization". Such a module still ships apps (`<apps>`,
with `id` as an attribute and a `placement` of MainMenu or AppMenu) and even
platform settings (`<settings>`), registered through ISettingsRegistrar
exactly as if IModule.Initialize had done it. Module catalog & install gains
the .NET vs frontend-only distinction plus the corollary: "my module loaded
but none of my code ran" usually means there was no assemblyFile.

Element names, resolution order and the exception messages are read from
ModuleManifest, ManifestApp, LocalStorageModuleCatalog and ModuleBootstrapper
rather than inferred.

Also adds an XML highlighter (comments, attribute values, tag names) since the
manifest is the first snippet that needed one. Verified lossless across all
snippets; multi-line XML comments stay single tokens and quotes inside them are
not mistaken for attribute values.

Modularity goes 8 tiles, which stays within 4 rows — poster still exactly
900px at 1440x900.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ame to DevOps

The "Your solution" layer now carries three ordered views: Solution
architecture, DevOps, Deployment schema. "Anatomy of a typical solution" is
renamed DevOps, since that is what it shows.

Adds a second diagram type. `schema` becomes `diagrams`, an ordered array
where `kind` picks the renderer — `stack` for the existing vertical
connector-pill layout, `flow` for left-to-right tiers of cards with dashed
clusters, badges, arrows and a legend. Channels migrates to the new field and
the checker rejects the old one.

The flow's structure, proportions and vocabulary (tier / node / cluster) follow
vc-module-solution-architecture-map, with two deliberate departures:

- Colours map onto this map's own tokens instead of that module's brand
  palette, so light mode keeps working. All flow text and badges clear WCAG AA
  in both themes (worst 4.58).
- No status dots. They signal live health there; on a static poster an
  all-green row would imply monitoring this page does not do. Ownership is
  carried by the kind-coded left border instead, and that border is the legend.

Content is generalised from the supplied customer solution — structure only, no
customer modules or naming. Solution architecture keeps the ≈80% standard base
/ ≈20% tailored split but states the useful distinction the original left
implicit: tailored work is either a NEW module or an EXTENSION of a standard
one (AbstractTypeFactory, dynamic properties, event handlers), plus
integrations. Deployment is cloud-neutral — edge & security, compute, data &
search, observability — with the split of one image into storefront / admin /
worker roles made explicit, since that is the part people get wrong.

A flow scrolls horizontally in its own box rather than wrapping; page has no
horizontal overflow and the poster still fits 900px at 1440x900.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… row

Adds the square-grid backdrop from the vc-release-notes hero. Copied from the
real thing rather than approximated: 60x60px cells, 1px lines, and the same
radial mask (#000 40% -> transparent 80%) so it fades toward the edges. It is a
fixed pseudo-element at z-index -1, which paints above the body background and
below all content, so no stacking order changed and it cannot intercept clicks
(verified with elementFromPoint). Panels and tiles are opaque, so the grid reads
only through the gaps.

Line colour is a token per theme — 4% white on dark as in the reference, 5%
ink on the light background. Text sitting directly on the grid still clears AA
(worst 5.37). The grid never prints: it is pure decoration and would only cost
toner.

Uses the Virto Commerce org avatar as the logo, replacing the "VC" text chip,
and as the favicon. Stored as a local asset so the page keeps making zero
external requests — verified: the only network call added is
assets/virto-logo.png from the same folder.

Also fixes a recurring layout defect the grid work exposed. Band tags wrapped,
so the band's height depended on how long a layer's tag strings happened to be,
which pushed the poster past one screen twice and had me tuning strings both
times. The tag row is now nowrap with clipped overflow and a soft right-edge
fade; every tag is still listed in full in the drawer under "Key pieces". Band
drops 118px -> 84px, the poster is back to 900px with ~20px of headroom, and
the height no longer depends on content.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…er overlap

Reworks the Solution architecture flow so the two front ends read as separate
paths all the way to the API, matching the storefront architecture reference:

- Presentation splits into Shopper / Employee lanes.
- Edge & routing: the shopper path is CDN → optional SSR/prerender → load
  balancer, which splits static content from API by route; the employee path is
  a firewall, because the back office is not published to the internet.
- API makes the isolation explicit — "Storefront — XAPI only" (with Content
  storage served by the static route, never through the platform) and
  "Back office — REST only".

Renames as requested: Out of the box → Virto Commerce Modules, Tailored to the
solution → Your Solution Modules, Data & jobs → 3rd party services, EF Core →
SQL → Database, Search index → Search, Distributed cache bus → Distributed
cache. Background jobs now names both engines (Hangfire default, RabbitMQ).

Fixes the overlap: cluster node grids use minmax(150px, 1fr) tracks while
.fl-node forced min-width: 172px, so every card was wider than its column and
spilled over its neighbour — measured two overlapping pairs before, zero after.
Nodes inside a cluster now let the grid size them.

Also corrects a description that was wrong, not just terse: XAPI was called
"experience reads" and "read-heavy" in four places. It is a business API with
queries *and* mutations — which is exactly why a storefront needs no REST — so
the wording now says so in the flow, the Channels stack, the API edge lead and
its comparison matrix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lls-style scopes

Replaces the per-stage cluster boxes with real swimlanes. Columns are stages,
rows are paths: Customer and Employee each get one continuous rail with its own
accent, running in parallel and converging only where they genuinely do — the
Modules and 3rd-party-services columns are marked `shared` and span both lane
rows. Repeating a "Shopper"/"Employee" box in every stage said the same thing
three times and still did not read as two isolated paths.

Within a lane each stage is one vertical column, so CDN / SSR / Load balancer
stack together and Content storage / XAPI stack together, as asked.

Cards are now genuinely identical: 196x84px everywhere, all 13 of them. Two
things were fighting that — flexible 1fr tracks stretched to fit unwrapped role
text (cards came out ~900px wide), and the shared column had no padding so its
cards were 16px wider than the ones inside a lane. Tracks are fixed at 212px,
role text clamps to two lines, and names ellipsize rather than wrap. The whole
diagram is 1309px, so it fits the expanded panel with no scrolling.

Module groups now render as scope boxes in the sense the Atomic Architecture
diagram uses for Cells: a bounded, dashed box with a title, a badge, the member
modules as individual chips and an explicit count. "Virto Commerce Modules — 15
modules" reads as a set of modules instead of leaving it to inference.

Renames the extension wording to "Virto Commerce modules extended via the
Extensibility Framework", and the shopper lane to Customer.

check-content.js validates the new kind: column labels, shared columns having
content, scopes listing modules, one cell per laned column, and that laned
columns precede shared ones — otherwise the grid indices the renderer computes
would silently be wrong.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…re rename

- Virto Commerce Modules scope now reads "100+ modules" via a `count` override,
  with the role line saying the chips are a sample. The chips were being read as
  the whole set when they are 15 of a much larger catalogue.
- Adds a Custom card to 3rd party services for what a given solution bolts on —
  payment gateway, tax service, ERP, email/SMS provider.
- Your Solution Modules: Loyalty becomes OrderExtension, Initial load removed.
- Export & import becomes Backup & restore (id, symbol Ei -> Br, name, tags),
  with all six cross-references updated.

On the rename: the platform still owns the contracts (IExportSupport /
IImportSupport are in Platform.Core), so the atom stays platform-badged, and a
note records that the platform-wide experience ships as VirtoCommerce.
BackupRestore 3.1003.0 (vc-module-backup-restore) today — confirmed in the
vc-modules registry — with a link to the repo.

The note also keeps a distinction the rename would otherwise have erased:
per-domain export/import is a separate concern with its own modules
(VirtoCommerce.Export, VirtoCommerce.Import, and the catalogue-CSV, price and
customer export/import modules). Backup & restore is not a new name for those.

Cards stay uniform at 196x84 across all 14, poster still 900px, and a stale
#/atom/export-import link falls through to the existing not-found panel rather
than rendering blank.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Restyles DevOps after slide 8 of the release-strategy deck rather than
approximating it: I read that slide's markup, which builds the diagram from
fl-lane columns each running node → mini pill → node, converging into
full-width rows separated by small uppercase pills.

A new `pipeline` diagram kind renders exactly that shape. Two parallel lanes —
Yours (Git → CI build → Custom modules artifact storage) and Vendor (Virto
releases → publish → Virto Commerce artifact storage) — converge on
vc-package.json, then the container image, then environments, with the deck's
"both storages feed →", "CD · restore & assemble" and "deploy" connectors.

Compact in the way that was asked for: 7.5-8px pills, 11.5px card titles, tight
padding, and stage carried by border colour instead of a legend. The whole
diagram is 348px tall against the previous stack's much taller ribbon, and it
still works in the docked 540px panel with the lanes side by side and no
internal scrolling.

vc-package.json is styled as the control point, because that is the slide's
actual argument: it decides which artifacts go into the image that is then
promoted unchanged across Dev, Stage and Prod.

Every pipeline colour clears WCAG AA in both themes (worst 4.54). The checker
validates node kinds and rejects lanes with differing step counts, which would
misalign the converging pill against one lane.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…list

The README still claimed Your solution carried a stack and two flows, and never
documented `lanes` at all — stale exactly where a maintainer would look first.
Lists the real kinds now (lanes / pipeline / flow, plus stack on Channels) and
documents lanes with the two constraints the checker enforces: laned columns
before shared ones, and one cell per laned column.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lf-width DevOps

Solution architecture refinements:

- Flow arrows between the stage headings, so reading direction is drawn rather
  than inferred from left-to-right order.
- XAPI renamed Business Logic (XAPI), which is what it is.
- Content storage moves out of API into Services (renamed from "3rd party
  services"), leaving the API column as just the two API surfaces.

The move needed more than relocation: Content storage is reached *directly* from
the load balancer's static route, in parallel with the platform, so placing it in
a later column would have implied it sits behind the API. It now carries a
"← direct from edge" badge and a dashed border, the legend gains a line-style
entry explaining that dashing, and the load balancer names both destinations —
static content straight to Content storage, API to the platform, both in
parallel. That parallelism is why the storefront stays fast and deploys
independently, so it is worth drawing rather than leaving to prose.

Legend entries can now describe a line style instead of a colour; a second
`infra` colour swatch would have said nothing. The checker skips kind-validation
for those but still requires a label.

Two fixes found by measuring rather than looking:

- The arrows were var(--border-strong) at 1.65:1 — technically present, visually
  invisible. Now var(--muted): 5.81 light, 6.7 dark.
- DevOps cards used --surface, which in light theme is the same white as the
  drawer, so only their borders showed. The pipeline now sits on a tinted panel
  with white cards, and the select/env steps carry emphasis through border weight
  and style rather than a fill that matched the new panel.

DevOps is also half width (660px of 1309) and centred, since it is a narrow
vertical story that did not justify the full span.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…top-align columns

Three fixes to the Solution architecture lanes.

**The flow arrows were rendering as "92" boxes.** The CSS held a control
character followed by "92" rather than U+2192: I had written the escape through a
Python replacement, where "\2192" inside a non-raw string is read as the octal
escape \21 (0x11) plus "92". It also leaked into the heading text, which is why
"Modules · atomic architecture" appeared with garbage on both sides. Now a
literal → glyph, consistent with the —, ✓ and ✗ already used in this stylesheet,
and immune to that class of mistake.

**API and Modules are now one grouped region** labelled "Virto Commerce". From
outside, the API surfaces and the modules behind them are a single thing, and
five equally-weighted stages implied five independent parts. The group is a
backdrop at z-index 0 behind the cells, declared in content by tagging columns
with a group id, so it needs no hard-coded indices.

**Columns top-align.** Cells were centring, so each column floated to its own
vertical middle and nothing lined up across the diagram — Content storage sat
well above CDN, which sat above Business Logic. Every first card now starts on
one line, level with the Services column, as asked.

The checker rejects a group that spans non-adjacent columns (the single box would
swallow columns not in it) and a column referencing an undeclared group.

Group label and arrows clear AA in both themes (worst 5.19), and the grouping and
alignment both hold in the docked panel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…l, grow scopes

Three refinements to the Solution architecture lanes.

**Arrows now sit with their own label.** They were absolutely positioned at the
column's right edge while the label is centred, so the distance between a label
and its arrow varied with label length — next to a short label like "API" the
arrow drifted far right and read as belonging to the following column. They are
inline after the label text now, so the pairing is fixed and the baseline is
shared by construction.

**The group label gets a band of its own.** A 15px spacer row sits between the
stage headings and the lane rows, and the group region extends up into it, so
"Virto Commerce" no longer crowds the cards beneath it — 17px clearance,
verified as no overlap.

The obvious way to make that space would have been top padding on the grouped
cells, but that would have broken the top alignment requested earlier by pushing
API and Modules out of line with Services. The spacer row moves every lane row
together, so all five columns still start on one line — checked in both the
expanded and docked panels.

**Scopes share the column height.** Your Solution Modules has fewer chips than
Virto Commerce Modules, so it ended short and left dead space below it. Both
scopes now flex-grow: 293px and 243px, with 8px left below (the cell's own
padding) instead of a visible gap.

Group label and arrows clear AA in both themes (worst 5.19).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A grid row is as tall as its tallest column, so the per-cell tint painted a filled
box down the full row height even where a column held a single card — 872px of
visible emptiness, worst cell 268px empty of a 352px box. Drop the tint: with no
box there is nothing to look empty. The lane is now carried by its label rail plus
one faint accent band spanning the whole row, rendered under both the group region
and the cards.

The group label was a bordered pill straddling the region's top edge, where it read
as a badge attached to the API stage heading above it. It is now plain text sitting
inside the spacer band below the border, so it names the region instead.

Verified at 1440x900: cells transparent, all 14 cards still 196x84, five headings on
one baseline, group still enclosing exactly API + Modules, label contrast 4.64 light
/ 6.01 dark, page 900px with no horizontal overflow, console clean, check-content PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ource

The two ownership colours were inverted in the build view: Custom modules drew the
Virto blue and the Virto artifact storage drew the your-code amber, so the diagram
contradicted the legend directly above it. Swapped, and the accent now runs down the
whole lane — heading, both card borders, both subtitles — so yours vs vendor is one
colour read rather than two borders to compare. The source cards were neutral grey
and carried no ownership at all; they now match their lane.

Virto releases / platform + modules -> Virto Commerce GitHub / platform + modules
source, which says where the artifacts come from rather than what they are.

Verified at 1440x900: amber rgb(227,161,58) on both Yours cards, blue rgb(99,164,255)
on both Vendor cards, all four still 220x46 on two shared baselines, subtitle contrast
5.14/5.19 light and 6.81/7.75 dark, heading 4.54/4.58 light, page 900px, check PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…urations

Replaces one hand-generalised deployment flow with the two ends of the published
sizing range, so the diagram answers "what do I actually deploy" for a developer
environment and for production, and the delta between them is the scale-out
conversation.

Both are `lanes`, deliberately the same visual language, so they read against each
other: S is one frontend instance and one backend instance with jobs in-process and
no Redis; XL splits the backend into commerce services, authoring and job workers,
each 1...n behind its own load balancer over one shared resource pool.

Content taken from vc-docs Fundamentals/Scalability - scalability-options (the S and
XL topology images, and the sizing table: S = 10 req/sec, 1 cart change/sec, 200
orders/day; L = 300 and 15 000) and scaling-configuration-on-azure-cloud (the
settings). Every setting named on a card is verified present on dev:
BackgroundJobs:Mode (Producer|Worker|Both), PushNotifications:ScalabilityMode,
ConnectionStrings:RedisConnectionString, and the ARR-affinity rule per app kind.

The source XL image labels its third environment "Content managers" twice; the prose
calls it the background-jobs environment, which is what the lane is named here. It
also draws Hangfire directly - this uses the engine-agnostic Mode lever instead,
which is what the platform exposes today.

Renderer: diagrams may now carry a `note`, rendered as a caption above the diagram.
Third lane accent `jobs` added for a lane nothing calls.

Verified at 1440x900: 4 blocks of diagrams render, all 14 XL cards and 9 S cards at
196x84, empty cells 1 (S) and 4 (XL) exactly where a path skips a stage, group region
over the Backend column, 1309px wide in the full-screen drawer with no horizontal
scroll, caption contrast 5.81 light / 6.70 dark, poster still 900px, console clean,
check-content PASS including --online.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…on title

"Deployment - production (XL)" -> "Deployment - production": the size letter is
sizing-guide vocabulary, and the diagram no longer matches XL exactly now that it
carries a fourth environment. The caption still names the Extra Large configuration
and says the integration lane is the split beyond the documented three.

Integration is its own lane rather than a card in an existing one, because it is a
distinct path: middleware calls REST machine-to-machine with no browser and no
session, skips the storefront entirely, and gets its own instance so bulk imports and
webhook delivery stay off the shopper path (Mode: Producer, ARR affinity off).

Needed a fourth lane accent. --lane-int is violet, added to all four token blocks and
deliberately outside the semantic ok/info/warn/bad set - it carries no meaning of its
own, it exists so no two lanes in a diagram share a colour.

Verified at 1440x900: four lanes in order Customer, Employee, Integration, Background
jobs, four distinct rails - blue, amber, violet, green - rail contrast 6.12 light /
6.08 dark, all 17 cards still 196x84, 1309px in the full-screen drawer with no
horizontal scroll, poster 900px with no overflow, check-content PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… backend roles

Inline <code> no longer carries its own face or size, so a chip like `Mode: Producer`
sits in the same typeface and on the same baseline as the words around it. A 12px mono
chip inside 10px card text was taller than its own line box and read as a different
font mid-sentence. font-family: inherit is required rather than implied: the UA
stylesheet puts monospace on <code>, so removing our declaration would have left the
UA's in force - the earlier measurement showed exactly that. Snippets are untouched;
.snippet pre sets the mono face itself.

Deployment - production:
- Commerce services -> Backend 4 Frontend, Authoring -> Backend 4 Employee. The group
  label follows: "Same image - 4 roles".
- Every platform workload is 2...n, not 1...n - two instances is the floor for
  availability, so the diagram should not show one as acceptable. Column heading and
  caption follow.
- The frontend carries no instance count at all. It is static content behind a CDN,
  so a count there implied a scaling lever that is not where scaling happens.
- SQL DB elastic pool now lists Main alongside Cart, Order, Catalog and Customer - the
  platform's own database is in the pool too.

Verified at 1440x900: inline chips match their context in all three sizes (10px card
role, 11.5px pipeline node, 14px list) with sameFamily and sameSize true; the C#
snippet still white-space: pre, Cascadia Mono 11.5px, 32 lines at 20px, 56 syntax
spans; all 17 cards 196x84 with no clipped names; group label ends at 1826 inside a
region ending at 1920; poster 900px, no overflow; check-content PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…vOps description

Deployment diagrams now follow the Solution architecture diagram rather than speaking
their own dialect:
- Same legend vocabulary and same order in all three - Your code, Virto Commerce,
  Edge & routing, Service, then the dashed line style. They were "Client & edge /
  Virto Commerce workload / Stateful service" in a different order, so the same colour
  had two names depending on which diagram you were looking at.
- The non-production diagram gets a group region round its backend column, the way the
  other two mark the platform: "Same image - all roles", against production's "Same
  image - 4 roles". The contrast between the two configurations is now the label.

DevOps gets the caption the other diagrams have, from the release-strategy framing:
the challenge, then the path in plain terms, then what it means for you - vc-package.json
as the control point, one image promoted across Dev, Stage and Prod.

Also, per review:
- Key pieces is right-aligned, heading and chips both, so its chips land on the same
  right edge as the diagrams below instead of stopping mid-row.
- Block titles up from 10.5px. Diagram and section titles (is-wide) are 14px in full
  text colour, so "Solution architecture" reads as a heading over its picture; ordinary
  field labels are 12px and stay muted, which keeps the hierarchy.
- Dropped the throughput figures from the S caption.

Verified at 1440x900: three legends identical, three group labels each inside their own
region and clear of the stage headings, all 40 lane cards still 196x84, captions 8/4/6
lines, poster 900px with no overflow, check-content PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A bulk replace of "body.textContent = ''" rewrote the line inside the new helper as
well, so clearDrawerBody called itself and every panel open blew the stack. The helper
now does what it says: disconnect the topology observers, then empty the body.

Verified: app.js parses, opening an atom then a layer then another atom leaves the
console clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Swimlanes answered "which path uses which stage". A deployment diagram has to answer
"what talks to what, over what" - so this adds a `topology` kind in the cloud-
architecture idiom: boxes on an authored grid, nested bounded regions, and labelled
orthogonal connectors.

Position is authored (col/row) rather than derived, because an architecture diagram's
layout carries meaning. Edges are measured from the laid-out boxes and drawn into an
SVG overlay, so they follow the real grid. Routing is orthogonal and turns in the
gutter immediately before the target, which is what keeps a long connector out of the
cards it passes - verified by sampling every path every 4px against every card box:
zero crossings in both diagrams. turnOffset fans parallel connectors apart; labelDx/Dy
is the escape hatch for two labels landing on one spot. Labels are knocked out of their
line with a paint-order stroke halo rather than a measured backing rectangle.

Non-production: browser to frontend to one platform app, jobs in-process, the employee
path dashed straight into the backend. Production: the top row is left empty as a
channel for the CDN-to-blob path, three client kinds converge on the load balancer, and
the four platform roles sit in one region against the shared pool.

Redraws are explicit - on open and on expand - not on a ResizeObserver. The panel is
`hidden` while its content is built, so there is no box to measure at that moment, and
observer callbacks are delivered with the rendering steps, which a hidden or
non-compositing tab does not run. That is why the first attempt drew nothing.

Also: the grid sizes to its content (fixed 170px tracks, width max-content) so the SVG
overlay covers the diagram instead of a box narrower than it, and captions flow into
two columns in full screen instead of leaving a block of empty panel beside them.

Verified at 1440x900: 7 and 15 edges each with a label, no path crossing any card, no
label overlapping another, no label outside the grid, all 24 boxes 170x58, panel scrolls
horizontally and full screen does not, redraw survives expand and collapse, console
clean, check-content PASS with new topology rules.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
OlegoO and others added 2 commits July 31, 2026 17:11
Adds a `tree` kind - a folder listing where every line is answered rather than just
shown - and uses it for the scaffold layout, since "which folder does this go in" is
the first question a new module raises. 31 rows, taken from the real scaffold at
vc-cli-module-template/templates/vc-module-dba-template rather than from memory, with
the descriptions saying why each folder exists: Core holds interfaces because the
implementations in Data are what a solution swaps, Data.Models are deliberately not
Core.Models, one migration project per provider and a module that skips one does not
run there, Web is the only project that is never a package.

Every item must carry a desc and depth may only step up one level at a time - both
enforced by the checker, because a missing parent silently reparents everything under
it. Layout is stacked in the panel and two columns in full screen, scoped to the panel
state rather than a viewport width: the constraint is the panel, and at 488px a 258px
path column leaves the descriptions a 200px measure.

Also corrects the XAPI repository: it is `vc-module-x-api`, and
`vc-module-experience-api` is the archived predecessor - verified through the API,
archived=true, last pushed 2026-05-29. The gotcha now says so, so nobody starts there.

Verified at 1440x900: 31 rows, no row without a description, no clipped path, panel
stacks with each description one step in from its path, full screen aligns all 31
descriptions at one x, no horizontal overflow, check-content PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread docs/atomic-map/app.js
function append(node, child) {
if (child === null || child === undefined || child === false) return;
if (Array.isArray(child)) { child.forEach(function (c) { append(node, c); }); return; }
node.appendChild(child.nodeType ? child : document.createTextNode(String(child)));
Comment thread docs/atomic-map/app.js
xml: {
// Comments first, then attribute values, then tag names — so a quote inside a
// comment stays part of the comment and text content stays plain.
re: /(<!--[\s\S]*?-->)|("(?:[^"\\]|\\.)*")|(<\/?[A-Za-z_][\w.:-]*)|(\/?>)/g,

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0c55dfe. Configure here.

Comment thread docs/atomic-map/app.js
var route = edgePath(local(from), local(to), edge.turnOffset);
var variant = edge.bypass ? 'bypass' : 'solid';
svg.appendChild(svgEl('path', { d: route.d, class: 'tp-edge is-' + variant,
'marker-end': 'url(#tp-arrow-' + variant + ')' }));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate SVG marker IDs

Medium Severity

drawEdges always creates markers with fixed ids tp-arrow-solid and tp-arrow-bypass. Opening Your solution renders two topology diagrams, so those ids collide in one document. marker-end urls resolve document-wide to the first match, so arrowheads can attach to the wrong SVG’s markers.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0c55dfe. Configure here.

@OlegoO OlegoO changed the title VCST-5644: Virto Atomic periodical table VCST-5644: Interactive Virto Atomic Periodic table Aug 4, 2026
OlegoO and others added 20 commits August 4, 2026 15:28
Dev teams score Virto's composability poorly, and the Architectural Guidelines give
them nothing to check: no boundary, no config key, no example of a solution actually
broken apart. The capability is real and undocumented. This makes it checkable.

Four new atoms, every claim cited:
- Db module-database. GetConnectionString(ModuleInfo.Id) ?? GetConnectionString(
  "VirtoCommerce") - verified in Pricing, Catalog, Cart, Order and the official
  scaffold. A per-module database is one config line. The platform does it itself for
  identity via Auth:ConnectionString.
- Xr cross-module-references. Ids, never foreign keys; values copied at the boundary.
  LineItemEntity holds ProductId, CatalogId, PriceId and Price as plain values with
  every navigation property inside OrderDbContext. This is WHY separate databases work.
- Od optional-dependency. optional="true" is skipped by the dependency solver, by the
  installed-check and by the failure cascade; IOptionalDependency<T> is the runtime
  half. A missing required dependency, by contrast, is a hard gate.
- Ho host-composition. One image, several hosts: module subset via a package manifest,
  role via configuration. The ceiling is the graph - Orders declares 11 dependencies.

Cells tier, below molecules, reserved. Six business capabilities anchored on their
experience API module, with membership read from vc-modules/modules_v3.json rather than
judgement, and the registry release recorded so a stale tile is visible. Infrastructure
modules already on the atoms tier are deliberately excluded - a cell is a business
capability, not a service it runs on. The splittable verdict is what the graph says:
own host (XCatalog, ProfileExperienceApi, XCMS), with catalog (XCart, XRecommend), with
cart (XOrder).

Your solution layer now answers the four questions directly: a Composition shapes
matrix (all-in-one, split by role, split by Cell, split the database, and "a module as
a service" marked not available), a Split by Cell topology, a Catalog pricing vs order
pricing topology, rung-by-rung bullets, and gotchas that state the limits plainly -
no distributed transaction, and migrations racing when the lock degrades to NoLock.

One correction found while authoring: the snippet cited IPricingService, which does not
exist. The real interface is IPricingEvaluatorService and it takes a
PriceEvaluationContext, not a product id.

The poster is still exactly 900px at 1440x900. The tier was paid for: the molecules
shelf became a chip row and the cell tile moved its subtitle to the panel, which bought
the 103px the tier cost.

Verified at 1440x900: page 900px with no horizontal overflow, cells in one row, all four
topologies with zero path-crosses-card and zero label overlaps and every card 170x58,
verdict-chip contrast 5.01/4.75 light and 8.09/8.48 dark, cell rules proven to fire by
breaking them, console clean, check-content PASS including --online.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Per the Architectural Guidelines ladder a molecule IS a module - "Catalog, Pricing,
Inventory, Search, Event Bus" - so the shelf now carries the real inventory: 51 modules
read from vc-modules/modules_v3.json, filtered to a stable 3.800+ release. Identity,
release, title and the full dependency list come from the registry; nothing is
hand-written, and the checker rejects a module tile that carries prose, because a
hand-maintained inventory of 51 modules is wrong within a month.

Excluded on purpose: Search and its engines, Assets and its stores, BackgroundJobs,
BackupRestore, EventBus, WebHooks, SeqLog, ApplicationInsights, the import/export
tooling, and the payment / tax / shipping / SSO / CMS adapters. The atoms tier already
covers the first group; the second implements a provider contract rather than a business
capability.

The 14 composite topics stay - around 60 atoms point at them through atom.molecule -
and are told apart by `kind`, with their own contract: a module tile needs moduleId,
version, group and dependsOn; a topic tile needs planned.

An emergent property worth knowing: searching a module name now surfaces every module
that depends on it, because the dependency list is in the search index. Searching
"xcatalog" returns XCatalog plus UCP, XCart, XOrder, XRecommend, Quote, Loyalty and the
rest of its dependents.

Also, per review of the solution layer: the Catalog-pricing-vs-order-pricing topology is
removed, and Split by Cell moves after Deployment - production and is renamed Composable
Architecture, since it only reads once the deployment shapes above it have been read. The
pricing answer itself is not lost - it is in the cross-module-references atom, which
carries the LineItemEntity snippet and the finding that vc-module-order has no backend
reference to Pricing at all.

The shelf is bounded at 54px and scrolls, because 65 tiles is more height than one screen
has to spend; the bound is in the section hint, and print removes it so the whole
inventory appears on paper. The poster is still exactly 900px at 1440x900.

Verified at 1440x900: page 900px with no horizontal overflow, 51 module tiles and 14
topic tiles, the module panel showing registry identity and dependency chips with
optional ones marked, group-chip contrast 5.19 light / 6.81 dark, three topologies on the
solution layer with zero path-crosses-card and zero label overlaps, console clean,
check-content PASS including --online.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ns read as prose

The diagram was a pile of boxes that did not say what makes a PBC split work. It now
draws the mechanism: the storefront keeps one endpoint and one GraphQL schema, the load
balancer resolves each operation and forwards it to the host that owns it - catalog
queries to the catalog host, cart mutations to the purchase host - and the four PBC hosts
meet again in the shared stores. That is the point worth drawing, because it is why the
frontend never learns about the split and why the split can be made later.

The description now answers when to do it. Split by PBC when one capability's load curve
is unlike the others, when a bad reindex must not be able to reach checkout, or when a
capability needs its own cadence, region or machine shape. Split by role FIRST - same
image, same modules, same database, one configuration change - and reach for a PBC split
only when that has stopped being enough, because this one is a second image to promote.
And the graph still decides: XOrder requires XCart requires XCatalog, so those three
deploy together whatever the diagram wishes.

Captions were 11.5px in two columns, which made the paragraph that explains each diagram
the smallest text on the layer. Now one column at 14px - the same size as every bullet -
with a 108ch measure cap, and a blank line in a note starts a new paragraph.

Two structural fixes found while measuring: a topology region label longer than its
region used to spill across the boxes beside it, so labels are now bounded to their
region and clipped; and the caption paragraph split had been written with a collapsed
escape, which put a literal newline inside a string literal.

Verified at 1440x900: all six captions 14px single-column, three topologies with zero
path-crosses-card and zero label overlaps and every card 170x58, no region label
overflowing or clipped, poster still 900px, console clean, check-content PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…readable lists

Composable Architecture gains the two boundaries it was missing. An outer dashed
"Cloud environment" wraps the load balancer, the four PBC images and the shared stores -
only the storefront sits outside it. Inside, each PBC host gets its own tight dashed box:
one deployable image, scaled and restarted on its own, which is what isolation means here.
A bad release of the catalog image cannot take checkout with it, and the caption now says
so.

The per-card box needed a new region variant. `tight: true` hugs one cell instead of
bleeding into the gutters, and carries no label - the box is only 10px taller than the
card, so a label went straight over it in all four cases, which is how the first attempt
measured. The replica count went back on the card where it fits, and the checker now
allows a label-less region only when it is tight.

Typography, per review:
- The Composition shapes matrix was 12.5/11.5px. It is the decision table a solution
  architect reads before choosing a shape, so it is set at body size, 14px, like the
  bullets around it.
- List markers were an em dash, which reads as punctuation belonging to the sentence.
  They are now a 5px drawn dot on the text baseline. The bad and warn variants keep their
  glyphs, because a coloured dot cannot say "avoid" or "careful".

Molecules lost its scrollbar, as asked: all 65 tiles are on the poster. It cost 50px, paid
by tightening the chips and taking `--tile` from 74px to 67px - the tallest atom family is
six rows deep, so every pixel there is worth six on the page. That is now written down in
the README as the dial to turn when a tier needs room.

Verified at 1440x900: page exactly 900px with no horizontal overflow, shelf not scrolling
with all 65 tiles in 5 rows, three nested boundaries on the Composable view with zero
region labels over cards (was four), zero path-crosses-card, zero label overlaps, every
card 170x58, matrix 14px, plain lists a 5px dot and the warn list still "!", console
clean, check-content PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The gutter is where a connector turns and where its label sits, and at 74px it was
narrower than the labels put in it - "reads · writes" is about 62px, so it ran into the
card beside it. Column gap is now 88px, and TP_GUTTER moves to 44 with it so turns stay
centred in the gutter rather than drifting into a card.

Image boundaries had the same problem vertically: a 12px row gap against 5px of bleed on
each side left them 2px apart, which read as one box with lines through it. Row gap 22px
and 7px of bleed puts them 8px apart, and the outer cloud boundary widened to clear them.

Both numbers are mirrored between styles.css and app.js, so the comment on each says to
change them together.

Verified at 1440x900, all three topologies: zero connector labels overlapping a card - the
actual complaint, and the metric that was silently non-zero before - zero path-crosses-card,
zero label-on-label overlaps, every card still 170x58, image boundaries 8px apart, and at
964 / 1222 / 964 px they all still fit the full-screen panel without horizontal scroll.
Console clean, check-content PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…eside it

Drawing "Background process" as its own card contradicted the caption right above it: the
Small configuration has one backend instance and the job engine runs in it. Two boxes said
two processes.

The card is gone. The Platform App now reads "REST · XAPI · Manager · jobs" with a
"×1 · all roles" marker, and the boundary round it is the same tight dashed image box the
Composable view uses - so "one image, all roles" is drawn identically in both diagrams
instead of once as a label and once as an extra card.

The caption says what the box costs rather than just what it contains: REST, XAPI, the
Commerce Manager UI and the job engine restart together, which is the actual reason this
shape is for proof of concept and developer environments and not for production.

Verified at 1440x900: 8 cards all 170x58 (was 9), 7 edges with no dangling reference after
the node was removed, zero path-crosses-card, zero connector labels on a card, zero
label-on-label overlaps, one outer environment boundary and one unlabelled image boundary,
console clean, check-content PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… the topologies

Assets were missing from the Small configuration, which made the step up to production
look like it added a service when it only changes a provider. The non-production diagram
now carries Blob storage with "local disk - FileSystemAssets" on it, so the jump reads as
what it is: the same dependency, a different provider, and one that stops working the
moment there is more than one instance.

Azure Blob Storage -> Blob storage in the production view. The service is the dependency;
the cloud behind it is a deployment choice, and the rest of that diagram does not name a
vendor either.

Composable Architecture had invented its own store list - Cart data, Order data, Customer
data - which made the same shared pool look like two different pools across two diagrams.
It now shares production's set exactly: Elasticsearch, Redis, SQL DB elastic pool, Blob
storage, Application Insights. Row order differs so every edge runs straight, and the
caption says the pool is the production one, that every image uses all of it, and that one
line each is drawn only to keep the picture readable.

Verified at 1440x900, all three topologies: zero path-crosses-card, zero connector labels
on a card, zero label-on-label overlaps, every card 170x58, no dangling edge after the node
changes, no two nodes sharing a cell, and the five-deep gutter fan on the non-production
diagram - five outbound edges from one card - lands without a single label collision.
Console clean, check-content PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…shirt size

Azure CDN -> CDN, for the same reason Azure Blob Storage lost its prefix: the service is
the dependency, the cloud behind it is a choice, and nothing else in that diagram names a
vendor.

The captions led with the sizing-guide letter - "The Extra Large configuration" - which is
vendor vocabulary a reader has to translate before the sentence means anything. They now
lead with the environment and keep the size as the citation it is: "The production
configuration - Extra Large in the sizing guide". Both captions changed, not just the one
asked about, because leaving Small in place would have made the pair inconsistent in
exactly the way that prompted the request.

Verified at 1440x900: both captions open with the environment, the card reads CDN, and all
three topologies still show zero path-crosses-card, zero connector labels on a card, and
every card 170x58. check-content PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…hor can act on

It stated a fact about someone else's module - Orders declares 11 required dependencies -
which tells a reader what went wrong somewhere else, not what to do in their own code.

It now leads with the rule and the recommendation: every required dependency has to be
installed in the package with the module, a missing one does not degrade it but takes it and
its dependents out of the host, so split your own dependencies into required and optional
and mark optional="true" on everything the module can start without. That is what makes a
module installable in several PBCs rather than only in the one carrying its whole list.

Orders stays, demoted to the counter-example it is - 11 required, none optional - because
the advice needs something concrete to point at.

Verified at 1440x900: the item renders at 14px over 8 lines with no clipping, both bold
phrases and the optional="true" code span intact, the other eight items unchanged.
check-content PASS, console clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…a Scaling atom

Production reads as big, and big reads as expensive. It is not: the unit is a small
container - roughly 1 vCPU and 512 MB - and the topology gets its capacity from having
several of them rather than from any one being large. .NET reads its container limits and
sizes the GC heap to them, and the platform keeps nothing on the request path worth
protecting - cache coherence, the SignalR backplane and locks all live in Redis - so an
instance can be added or replaced without ceremony. The caption now says that, with the
discipline it demands: bursty work (a reindex, a bulk import) belongs on the jobs
environment sized for it, not on a 512 MB request host.

New `kind: 'section'` - prose inside the diagram sequence, so an explanation can sit
between two diagrams. Scaling uses it, between the two deployment views: out before up,
then one line per level with the lever that actually moves the number. Frontend out
without limit; API and PBC hosts out by replica count; jobs often up first, because a
reindex is one large unit of work; Redis a prerequisite rather than an optimisation, and
scaled up before out because a cluster changes the semantics; database up first, then read
replicas, then a database per module; search out as a cluster because it is the read path;
blob storage out by definition once local disk is gone.

New atom Sc scaling (Infra & ops), every lever a real key or file on dev, including one
worth knowing: `Connect Timeout` in the connection string becomes the *command* timeout,
because DbContextRepositoryBase reads it off the connection and calls SetCommandTimeout.

Two things the work forced:
- The staleness rule was wrong. meta.js documents "bump platformVersion on a sweep, bump
  verifiedAgainst per atom you re-check", but the checker required equality - which forces
  a choice between lying about the platform version and lying about which atoms were
  checked. It now rejects an atom claiming a version NEWER than the sweep and reports how
  many are behind: 63 of 64, against 3.1059.0, which is the honest state of the map.
- The 64th atom pushed Infra & ops to 13 atoms, a seventh grid row, and the page to 968px.
  TimeProvider moved to Execution & async, where it belongs anyway - it is the clock async
  code schedules against, next to cancellation and AsyncLock - and that put the poster back
  to exactly 900px.

Verified at 1440x900: page 900px with no horizontal overflow, 64 atoms, the Scaling section
between production and Composable with 7 items at 14px and no clipping, the production
caption in two paragraphs, the atom panel showing 7 API rows and a 23-line JSON snippet with
24 syntax spans, every cited member re-grepped in its file, console clean, check-content
PASS including --online.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`.layer` set `align-items: flex-start`, so each child sized to its own content instead of to
the card. The tag row took its max-content width - wider than the card - which meant the
`overflow: hidden` on it had nothing to clip against, and the chips painted across the
neighbouring card: "Module" over Integration, "ERP/WMS/CRM" and "SQL" over Infrastructure.

The container now stretches its children, and the row gets `min-width: 0` so it may shrink
below min-content. The .cell tiles were always correct for the same reason in reverse - they
never overrode the default.

Verified at 1440x900 with an audit worth keeping: for all 142 cards on the poster, does any
descendant paint outside its card? Zero, once boxes clipped by an ancestor are excluded -
nine layout boxes extend past the card and all nine sit inside the clipping row, which is
the intended behaviour rather than the bug. Tag rows now 173px inside a 194px card and
clipping; band still 84px, poster still 900px, no horizontal overflow.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The layer described integration as a technique - middleware, events, webhooks - which is
the wrong first question. The Integration Capabilities deck has the better frame and it is
the one the business already uses: integration is a different problem for each audience,
and handing all three the same generic API is how budgets disappear. So the layer now leads
with "match the door to whoever is knocking" and carries the decision itself as a ten-row
matrix, each row a door with the case that picks it.

Ordered the way the deck orders them, cheapest first: Admin UI configuration (check this
door every time - the most expensive integration is the one that never needed to exist),
xAPI for a person on a screen, REST for a system you already own, integration middleware
for the back office - the normal architecture for multi-system landscapes, not the fallback
- Punchout for the buyer's procurement system, UCP over MCP for an AI agent, then the three
supplier doors: vendor APIs, vendor middleware for the long tail, and the Vendor Portal for
suppliers with no IT at all. Events out is listed last because it is not a door in.

The proof line from the deck is in as a bullet, dated: HEINEKEN, 250 integrations across 25
operating companies, and hundreds of enterprise-system integrations across the customer base
as of 2026-06-30.

One correction to the source. The deck names a "Punchout module". There is no such module in
the public registry and no public repository for it, so the layer records that in its
gotchas - a delivered capability to scope with the Virto team, not a package anyone can put
in vc-package.json. Everything else was checked: VirtoCommerce.UCP 3.1004.0 exists, and both
new external links return 200.

Reference added, as asked: the presentation itself, plus vc-module-ucp and ucp.dev.

Verified at 1440x900: ten matrix rows at 14px with nothing clipped, five doc links all
opening off-site, six band tags on the layer card, poster still 900px, console clean,
check-content PASS including --online.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The layer listed the services and stopped, which leaves the harder half of the question
open: somebody has to operate all of it, and that decision costs more than the service
list. The Virto Cloud deck answers that half, so the layer now carries the deck's own
comparison as its decision matrix - nine criteria, self-hosted against Virto Cloud, with
the concrete difference on each: environments in weeks or minutes, a rollback you design
and test or blue-green with instant rollback, a platform team you staff or platform skills
that come with it.

Three platform-relevant facts from the deck joined What lives here: the runtime is a
Kubernetes-native core with AKS, SQL, Redis and Elasticsearch as managed services, portable
to AWS and GCP on request but not simultaneous; composable scaling is an infrastructure
property, with catalog, pricing and search scaling independently, horizontal automatic and
vertical manual; and the environment is a Git artifact reconciled by Argo, which makes a
rollback a revert rather than a rebuild - the practice worth copying first if you self-host.

Everything from the deck is attributed to Virto Cloud rather than to the platform, because
that is the distinction the layer exists to make: the services are what the platform
requires, Virto Cloud is one way to have them, self-hosting is the other.

Four gotchas added that vendor material does not volunteer. The load-bearing one: 99.9% on
the platform layer is not 99.9% on your solution - a custom module that throws or a
migration that locks a table is inside your half of the boundary, and that line matters
before anyone quotes a number to a customer. Also: managed is not unwatched, "on request"
is a project with a date rather than a switch, and the two numbers worth asking to see
measured on your own workload are the performance claim and the achieved uptime, not the
target.

Reference added, as asked.

Verified at 1440x900: nine matrix rows at 14px with nothing clipped, 21 emphasised phrases
intact, eight bullets, five doc links, six band tags, poster still 900px, console clean,
check-content PASS including --online, and the presentation URL returns 200.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… actually link

The atom answered "which dial do I turn" but not the question a solution architect is
asked first: is this thing ready to scale at all? It is now `scalability`, and When to use
leads with the readiness assessment, per level rather than per platform.

Ready to scale out with no work: the request path, the memory cache once Redis carries
invalidation, jobs via Producer and Worker hosts, a host per module subset, a database per
module, search as an external cluster, assets behind a cloud blob provider. Ready to scale
up only: the relational database - one writer - a single index rebuild, a single large
import; more instances do not divide that work. Not ready, and no configuration changes it:
a transaction spanning two module databases, and a single module as its own service. Both
are design constraints, not settings.

Avoid now names the four prerequisites for the second instance, each of which works on one
instance and fails on two: Redis configured, a cloud blob provider instead of
FileSystemAssets, a real search provider instead of Lucene, and the bearer-token
certificate shared.

Performance & scale-out retired from the molecules shelf, as asked - the atom holds that
content now. The four atoms pointing at it were repointed rather than left dangling:
scalability to deployment, and the three caching atoms lost a field they no longer needed.

A defect this surfaced: `[[atom-id]]` had been written in four places on the assumption the
renderer resolved it. It did not - it rendered as literal `[[host-composition]]` text on the
page. rich() now renders it as a link to that atom, with the atom's one-liner as the title,
and the checker sweeps every string in every content file and rejects a reference that does
not resolve. Written as an anchor rather than a button because rich() output lands in divs
and list items, never inside a button, where nesting would be invalid.

`updated` actualised to 2026-08-12.

Verified at 1440x900: 64 atoms, poster 900px, no horizontal overflow, zero literal `[[`
anywhere on the page, both cross-references rendering as links whose hrefs navigate - the
click was followed and the panel changed to "Ids, not foreign keys" - contrast 5.19 light
and 6.62 dark, molecules down to 64 with the topic gone, header reading "verified against
platform 3.1059.0 · updated 2026-08-12", console clean, check-content PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… container guidance

Update process, on Your solution, after DevOps - build and ship is the first half of the
lifecycle and staying current is the second. From the release-strategy deck: Stable every
~3 months with full regression, Edge daily with automated testing only, and the rule that
Stable runs production. The point that matters most is that **the default update is a
manifest edit** - bump a version in vc-package.json, vc-build update, rebuild your modules,
test, deploy - and that this only holds because you extend rather than modify. When code is
affected the size is known up front (S / M / L), three things make a step genuinely bigger
(.NET LTS every ~2 years, the yearly third-party refresh, a security-driven breaking fix),
hotfixes reach the two latest Stable lines, and a feature can be adopted from Edge onto
Stable now and folds in later. Reference added.

Integration gains the deck's three audience schemas as topologies, in the styles already
here: buyers, the eCommerce team, suppliers. Each is the same shape - caller, the door it
knocks on, the platform behind it - because the repetition is the argument. The matrix said
"match the door to whoever is knocking"; now there is a picture per audience.

Scalability now carries Microsoft's guidance rather than my inference, all from Learn:
Server GC is the ASP.NET Core default and is **not available on a single core**, so a
1-vCPU container silently runs Workstation GC - which Microsoft says is often the better
choice for small containers and high-density hosting, with their sample dropping the
working set from ~500 MB to ~70 MB. DATAS has been on by default since .NET 9 and sizes the
heap to the application rather than the machine, ~80% working-set improvement for a 2-3%
throughput cost. In a container the GC treats the container limit as physical memory, with
a 75% default heap hard limit, and for a small process Microsoft suggests raising
HighMemoryPercent rather than lowering it. GC settings are read once at start and are per
process. Four Microsoft pages added as references.

Per-module databases now stated as their own line rather than three words in a list: each
module registers its own DbContext and resolves its connection string by module id, so
`ConnectionStrings:VirtoCommerce.Pricing` moves Pricing to its own server - migrations and
all - and it works only because modules hold ids and copied values across a boundary,
never foreign keys. The snippet shows both that and the runtimeconfig side.

A new checker rule earned its place immediately. An edge label of 22 characters overhung
its 88px gutter onto the cards either side - the same class of defect fixed by hand two
commits ago. The rule flags any label over ~18 characters, and on its first run it caught
three more I would have shipped.

Verified at 1440x900: all three integration schemas with zero path-crosses-card, zero
labels on a card and every card 170x58; Update process rendering 2 caption paragraphs and
7 items at 14px with no clipping; the Scalability panel with a 42-line snippet, 42 syntax
spans, four working cross-reference links and no literal [[; poster 900px; console clean;
check-content PASS including --online.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
HybridCache / IDistributedCache -> Hybrid Cache, and Platform memory cache -> Cache. Both
were the longest names in the Caching family and both wrapped to two or three lines on an
82px tile; the poster reads better when a tile name is a name rather than a type signature.
The ids are unchanged, so every deep link still resolves.

One thing the rename would have broken: the Hybrid Cache atom's "Use instead" pointed at
"the Platform memory cache atom" by its display name, which no longer exists. It is now a
[[platform-memory-cache]] cross-reference, so it renders as a link and the checker holds it
to a real atom rather than to a string that can drift again.

Verified at 1440x900: all six Caching tiles uniform 82x67 with every name on one or two
lines and none clipped, the family column down from 243px to 237px, poster still 900px,
the panel title reading "Hybrid Cache", See also and the new link both showing "Cache",
console clean, check-content PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lasses and vc-crud

Dependency injection led with override-by-last-registration and left lifetimes as two
warnings, when choosing a lifetime is the decision a developer makes every time they add a
service. It now leads with which one and why: transient as the default for stateless
services, scoped for anything needing one consistent view of the data for a request,
singleton for expensive or genuinely global state with the two obligations that come with
it - thread-safe, and holding nothing scoped. Plus the point that matters in this codebase:
work with no request to borrow a scope from opens its own, which is what
BackgroundJob.Enqueue does and what the Func<IPlatformRepository> registered beside the
repository is for. Four Microsoft pages linked, and the honest ratio on dev - 76 singleton,
26 transient, 13 scoped - with the note that almost all of those are infrastructure, not a
licence for your own services. Scope validation only runs in Development, which is worth
knowing before production is where the captive dependency shows up.

Module federation: you did not miss it - there was no atom and no tile, only two prose
mentions of VC-Shell on the Channels layer. It is platform code and deserved one.
AppManifestService walks the topologically sorted module list, probes each module for
plugin.json descriptors targeting the requested app, and returns a descriptor whose Hash
covers the app version, every plugin entry and content file, the federation coordinates and
the permission-filtered subset - used as an ETag, so a shell revalidates with one field
read. GET api/apps/{appId}/manifest. Cached per app id and bypassed in Development so a
yarn build appears on the next fetch. `platform` is reserved for the legacy AngularJS shell.

Generic CRUD now says the thing a developer needs to hear first: **do not write it**.
CrudService has exactly one abstract member, LoadEntities, and SearchService exactly one,
BuildQuery - Get, Save, Delete, response groups, cloning, cache regions and the
changing/changed events are all inherited. And there is no need to assemble even that:
vc-cli-module-template ships templates/vc-crud-template, shortName `vc-crud`, which emits
the model, search criteria and result, both events, the EF entity, DbContext, repository,
both services, an API controller and Module.cs from an EntityName symbol.

One correction found by --online: I cited Fundamentals/Persistence/generic-crud, which does
not exist in vc-docs. Repointed at the DB-agnostic custom-module guide, which does.

Verified at 1440x900: 65 atoms, poster 900px, no horizontal overflow, the Fe tile 82x67 with
nothing clipped, the CRUD panel showing a 38-line C# snippet with 67 syntax spans and seven
API rows, two working cross-references, no literal [[ anywhere, console clean, check-content
PASS including --online.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`path: 'superpowers/specs/...'` resolves only from inside this repository, which made those
six links a hidden dependency on the map's own location. They are now GitHub URLs on dev, so
they work from anywhere — including the copy that now lives in vc-release-notes.

check-content PASS, and a sweep of the loaded content confirms no doc link is relative any
more: all 179 are absolute URLs or vc-docs pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants