VCST-5644: Interactive Virto Atomic Periodic table - #3096
Conversation
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>
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>
| 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))); |
| 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, |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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.
| 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 + ')' })); |
There was a problem hiding this comment.
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)
Reviewed by Cursor Bugbot for commit 0c55dfe. Configure here.
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>


Description
Interactive Virto Atomic Periodic table
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.htmlposter (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.jsrenders everything fromcontent/*.jswithout dependencies: DOM viael()(notinnerHTML), lightweight C#/JSON/XML highlighting, doc links derived from vc-docspagepaths, GitHub links forapi[].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.jsvalidates schema, cross-references, repo paths, diagram structure, registry-backed molecules/cells, and optional--onlinevc-docs page checks.The map is meant to publish with MkDocs via awesome-pages; maintainers edit data in
content/atoms.jsand runnode docs/atomic-map/check-content.jsto keep references honest.Reviewed by Cursor Bugbot for commit da9c507. Bugbot is set up for automated code reviews on this repo. Configure here.