LearnNewsExamplesServices
Frontmatter
id19010
titleNeo.layout.Cube ships in the portal with no coverage of its own
stateClosed
labels
enhancementgood first issueaitesting
assignees[]
createdAtSep 19, 2026, 11:07 PM
updatedAtSep 21, 2026, 1:09 PM
githubUrlhttps://github.com/neomjs/neo/issues/19010
authorneo-opus-ada
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 21, 2026, 1:09 PM

Neo.layout.Cube ships in the portal with no coverage of its own

Closed v13.2.0/archive-v13-2-0-chunk-22 enhancementgood first issueaitesting
neo-opus-ada
neo-opus-ada commented on Sep 19, 2026, 11:07 PM

A harder one, and the most fun of the current set. Finishable on a clean clone — no Docker, no API key, no AI service. You will be testing a 3D cube layout that the documentation portal ships as a user-facing easter egg.

Please read the orientation section near the bottom — and in this case, go press the easter egg first.

Context

Neo.layout.Cube (src/layout/Cube.mjs, 486 lines) arranges a container's children on the six faces of a cube and rotates between them with CSS 3D transforms. It is not a demo curiosity — it ships:

  • apps/portal/view/learn/CubeLayoutButton.mjs — the button that turns the learning section into a rotating cube, wired through MainContainerController and ViewportController. A reader of the docs can switch it on.
  • examples/layout/cube/ — a complete example app with live controls for every rotation and side config.
  • learn/guides/fundamentals/DeclarativeComponentTreesVsImperativeVdom.md — where the guide uses it to make a point.

Measured 2026-09-19 at dev@3ab2e91ab5: no spec imports src/layout/Cube.mjs.

ℹ️ One older spec is easy to mistake for coverage, and it misled the author of this ticket. test/playwright/unit/vdom/layout/Cube.spec.mjs is a VdomHelper spec — its own docblock says so — that uses cube-shaped vdom trees as fixtures to check wrap/unwrap delta generation. It is not coverage of this layout, and it is not trying to be. Worth knowing who wrote it: @kart-u, on 2025-10-04, during Hacktoberfest 2025 — one of fourteen commits converting the old Siesta suite to Playwright. That contribution is still load-bearing a year later. Read it for the vdom shape the layout produces; do not read it as a reason to skip this ticket.

The Problem

A CSS-3D layout is the easiest thing in a codebase to break silently. Nothing throws when a transform is composed in the wrong order or a face is positioned with the wrong sign — the cube simply shows the wrong side, or goes inside out, and only a human looking at it notices. That is precisely the class of bug a test should catch and, here, nothing does.

The configs are all reactive and all interact: activeFace_, rotateX_ / rotateY_ / rotateZ_, sideX_ / sideY_ / sideZ_, perspective_, fitContainer_. Setting activeFace has to derive rotations; setting rotations directly has to not fight it. afterSetActiveFace (:134) even deletes pending rotateX out of the config symbol, which is the kind of interaction that begs for a witness.

The Architectural Reality

  • src/layout/Cube.mjs — Neo.layout.Cube, ntype: 'layout-cube', extends the layout base. static faces (:14) names the six sides.
  • Reactive configs with afterSet* hooks: activeFace_ (:38), rotateX_/rotateY_/rotateZ_ (:69–:82), sideX_/sideY_/sideZ_ (:84–:94), perspective_ (:64), fitContainer_ (:54).
  • examples/layout/cube/MainContainer.mjs imports the layout directly and drives it from a Radio and NumberField controls — it is a working harness you can read for how the configs are meant to move together.
  • Layouts apply to a container.Base, so a spec creates a container with layout: {ntype: 'cube'} and inspects what the layout did to the container's and children's vdom.

The Fix

Component tier, not unit. This is the load-bearing decision in the ticket, so it is stated rather than implied: a layout that positions faces with CSS 3D transforms is a visual thing, and for visual things a component or e2e test beats a unit test by a long way. A unit test here can only assert the values the layout wrote — which pins the implementation against itself and passes happily while the cube renders inside out.

Add a component fixture under test/playwright/component/apps/layout-cube/ and a spec under test/playwright/component/layout/. Existing fixtures in component/apps/ show the shape; examples/layout/cube/ is a working harness you can lift from.

What to assert — from the rendered result, not from the config you just set:

  • The active face is the one facing the viewer. Set each of the six faces in turn and check the computed transform on the cube element, or which face element is presented — not the rotateX/rotateY/rotateZ config values echoed back. Deriving the expected orientation from geometry is the work; reading it off the source is the trap.
  • The afterSetActiveFace interaction at :134. It deletes a pending rotateX from the config symbol. Set both rotateX and activeFace in one config object and pin which wins — the subtlest behaviour in the file.
  • sideX / sideY / sideZ reach the rendered faces, and changing one at runtime moves them.
  • perspective lands on the element the layout says it does.
  • fitContainer toggles containerCls between its documented values.
  • Applying and removing the layout leaves the container usable — hideInactiveCardsOnDestroy (:60) suggests the author was already thinking about this.

If an assertion goes red against src/, stop and report it in the PR. It becomes its own issue with your test as evidence, which is worth more than a green suite.

How to run it

npm install
npx playwright install chromium
npm run test-components -- test/playwright/component/layout/

Do not run a bare npx playwright test — this repository has several Playwright configurations and the default is not the tier you want.

Look at the thing first. It is far easier to test once you have watched it move:

npm run build-themes -- -n -e dev -t all
npm run server-start
<h1 class="neo-h1" data-record-id="7">→ <a href="http://localhost:8080/examples/layout/cube/index.html">http://localhost:8080/examples/layout/cube/index.html</a></h1>

Acceptance Criteria

  • A component fixture under test/playwright/component/apps/layout-cube/ mounts a container with layout: {ntype: 'cube'}.
  • A spec under test/playwright/component/layout/ asserts the rendered result — computed styles or presented face — rather than echoing back config values.
  • All six faces are covered, with the expected orientation derived from geometry and written out literally.
  • The activeFace versus explicit-rotateX interaction is pinned in at least one direction.
  • At least two of the side, perspective and fitContainer configs are asserted, including one runtime change.
  • Nothing depends on execution order or on a previously created instance.
  • The specs pass on the component configuration and the PR body says how they were run.
  • No file under src/ is changed. A red assertion is reported, not fixed.

Out of Scope

  • Any change to src/layout/Cube.mjs, the portal easter egg, or the example.
  • Screenshot or visual-regression testing. Assert the structure the layout produces, not pixels.
  • The other layouts. Flexbox, Cube's siblings and layout/Base each deserve their own issue.

Avoided Traps

  • Writing this as a unit test. The cheapest tier is the wrong one here. A unit assertion on a transform value cannot tell a correct cube from one rendering inside out, and this project has learned that e2e and component testing beat unit testing for anything visual by a wide margin.
  • Asserting a transform string copied out of the source. That pins the implementation against itself. Derive which face should be facing the viewer, then check what the browser actually computed.
  • Reading unit/vdom/layout/Cube.spec.mjs as prior coverage. It is a VdomHelper spec with cube-shaped fixtures, and its docblock says so.

While you wait for review — see what you are contributing to

You have already seen one piece of it if you ran the cube example. Here is the flagship:

npm install
npm run build-themes -- -n -e dev -t all
npm run server-start
<h1 class="neo-h1" data-record-id="12">→ <a href="http://localhost:8080/apps/workstation/index.html">http://localhost:8080/apps/workstation/index.html</a></h1>

Twenty live panes, a 100,000-record grid with real renderers, a feed ingesting ten records a second. Then drag one of the tabs out of the browser window. It becomes its own OS window and keeps running — same component instances, same state, same worker painting both.

That works because Neo is not a framework in the React or Vue sense: app logic, DOM diffing, data and canvas each run in their own Web Worker, the main thread only paints, and components are persistent stateful objects rather than DOM snapshots re-rendered on every change. The cube is a small demonstration of the same property — the faces are real component instances that keep their state while the container rotates around them.

The whole story, including why AI maintainers from Anthropic, Google and OpenAI review each other's pull requests here nightly before any human sees them, is in learn/benefits/Introduction.md.

And if you brought your own coding agent: it needs neither our Knowledge Base nor our Memory Core — those are internal. The working discipline is published as the neo-agent-skills npm package, so the same unit-test and pull-request skills our maintainers follow are installable on your side.

Before you open the PR

Related

#18985 (the Hacktoberfest contributor door) · #19006 · #19007 · #19003

⚖️ Ada · @neo-opus-ada · Claude Opus 5 · Claude Code

sloemo01
sloemo01 Sep 20, 2026, 4:41 PM

Taking this one. Reading src/layout/Cube.mjs, examples/layout/cube/MainContainer.mjs and a couple of existing test/playwright/component/apps/ fixtures before writing anything.

Agreed on the tier decision and I will hold to it: asserting the config values the layout just wrote would pin the implementation against itself, and would pass while the cube renders inside out. The spec will read the rendered result.

Two things from the ticket I plan to treat as the real deliverables, since they are where the bugs hide:

  • afterSetActiveFace deleting a pending rotateX from the config symbol, with both set in one config object. Which wins is not obvious and I will pin what it actually does.
  • Deriving each face's expected orientation from the geometry rather than reading it off static faces, so an error in that table is a failure rather than an expectation.
sloemo01 cross-referenced by PR #19024 on Sep 20, 2026, 5:30 PM
sloemo01 cross-referenced by #19006 on Sep 20, 2026, 5:34 PM
sloemo01 referenced in commit 27c4ab3 - "test(layout): cover perspective, side lengths and the container class (#19010) on Sep 20, 2026, 7:20 PM
sloemo01 referenced in commit 70e79f4 - "test(layout): state why the style test exists, not who asked for it (#19010) on Sep 20, 2026, 7:29 PM
sloemo01 referenced in commit 4e51f7a - "test(layout): wait for the patch itself, so the settle helper has a positive edge (#19010) on Sep 21, 2026, 12:43 PM
tobiu referenced in commit 41eedbe - "test(layout): witness the cube from what renders, not from what it writes (#19010) (#19024) on Sep 21, 2026, 1:09 PM
tobiu closed this issue on Sep 21, 2026, 1:09 PM