A form in Neo.mjs spaces its fields with one CSS rule. Some fields set their own bottom margin with a rule of exactly the same strength, so whichever stylesheet the browser loaded last wins, and the same form renders two ways depending on what the app happened to create first. You'll make the outcome deterministic with one selector change, and write the browser test that proves it.
This is a good first issue for Hacktoberfest 2026. It needs Node.js and a clone of this repository: no Docker, no AI setup.
[!IMPORTANT]
Reserved for first-time Neo.mjs contributors. The maintainer team will not claim, edit, or implement this issue through 2026-10-31. Comment here to claim it, and ask anything on this issue.
resources/scss/src/form/Container.scss gives every direct child of a form margin-bottom: 10px through .neo-form-container > *, which has specificity (0,1,0). Two field roots set their own bottom margin at the same specificity:
.neo-textfield sets margin-bottom: 5px in form/field/Text.scss. Number, Date, Time and the other fields that extend TextField inherit it.
.neo-file-upload-field sets margin-bottom: 2.5rem in form/field/FileUpload.scss, which reserves room for its error message.
Neo loads each class's stylesheet the first time a component of that class is created (Neo.worker.App#insertThemeFiles). Create the form first, and Text.css loads after Container.css, so fields get 5px. Create any TextField earlier, even a hidden one, and the order flips, so the same fields get 10px. It surfaced in neomjs/neo-agent-institution#212, where a hidden card moved every field of another form by 5px.
The fix (decided)
The field governs. Wrap the container's base rule in :where(), which contributes zero specificity:
:where(.neo-form-container) > * { margin-bottom: 10px; } // children that set no margin of their own.neo-form-container > *:last-child { margin-bottom: 0; } // unchanged, (0,2,0)
Add one comment line in the SCSS saying why: a field's own margin wins, and the container spaces only the children that set none.
Why this option, not "the container governs" (decided 2026-10-02 by @neo-opus-ada; @tobiu, the rule's author, was open to either):
It settles FileUpload too. Its 2.5rem is a functional reserve. Under "the container governs" it would still tie with the container rule, so it needs a special case.
It keeps what most forms render today. A form usually loads Container.css before its fields' stylesheets, so its fields already show 5px.
The rhythm already reads as 10px. A form lays out as a vbox (flexbox), where margins don't collapse, so two stacked text fields show 5px + 5px of space.
Where things live
The rule: resources/scss/src/form/Container.scss.
New test file: test/playwright/component/form/Container.spec.mjs.
Pattern to copy: test/playwright/component/form/field/Text.spec.mjs. It loads the empty test viewport, a fresh page per test, so each test starts with no stylesheets. It creates components with Neo.worker.App.createNeoInstance(...). The form's ntype is form-container, and the upload field's is file-upload-field.
Done when
Container.scss uses :where(.neo-form-container) > * for the base rule, keeps the last-child rule as is, and says why in one line.
Container.spec.mjs builds the same form twice: three children, a TextField, a FileUpload field and a TextField, in that order.
In one test, create the form first.
In the other, create a standalone TextField and a standalone FileUpload field first, then the form.
Both tests read getComputedStyle(...).marginBottom and assert the same values: 5px for the first TextField, the FileUpload field's own 2.5rem (in px) for the upload field, and 0px for the last child.
On today's dev, the second test fails: the first TextField reads 10px. That's the bug, caught.
npm run test-components -- test/playwright/component/form/Container.spec.mjs passes, and so does npm run test-components.
The PR says which of the 10 files under examples/ and apps/ that import form/Container.mjs render differently afterwards. Only forms that ran after a TextField existed elsewhere should change. Add one before/after screenshot if any does.
Out of scope
Other equal-specificity ties in the theme, and when or how theme files load. Insertion follows creation order by design, so the fix belongs in the selector.
While you wait for review
You just made one rule deterministic in a multi-threaded application engine. In Neo.mjs your app's code runs inside a web worker, and the main thread only paints, which is why stylesheets arrive in the order components are created. Two things worth ten minutes:
See the part nobody expects. Run npm run build-themes -- -n -e dev -t all and npm run server-start, open apps/workstation/, and drag a tab out of the browser window. It becomes its own OS window, still running, with the same component instances and the same worker. Benefits: Introduction explains why that is possible.
Your coding agent can go much further here. The skills this repository's AI maintainers work with are published on npm as NEO_INLINE_CODE_40, and they need no Knowledge Base and no Memory Core. With Claude or GPT beside you, the help wanted issues are within reach after this one.
Post-Merge Validation
The maintainers check this one; the contributor doesn't. On the next neo pin bump in neomjs/neo-agent-institution, the Agent OS goldens should not move, because its forms take the form-first path. If one moves, re-capture it and say why.
Maintainer notes
Filed 2026-09-25 while fixing neomjs/neo-agent-institution#212's visual gate. Measured in the Agent OS app on Institution devd93f7db4f2 (Chromium, the visual config):
form/Container.css at link index 77 and form/field/Text.css at 80 gave the fields 5px.
With a TextField created earlier, Text.css moved to 29 and Container.css to 88, which gave 10px.
History: @tobiu's a748a94664 (2020) shipped the rule in one bundle that imported field/Text before Container, so the container rule always won. Per-class theme files turned that fixed order into a race.
Precedent: #14970 made a dock rule order-independent the same way.
Re-verified 2026-10-02 on dev: no commits to the three SCSS files or src/worker/App.mjs since filing, no Container.spec.mjs, and no PR names this issue. Curated under #18985.
Origin Session ID: 0f80515e-7682-4313-8101-b926da48c55c · converted to a good first issue in session 6f7d14a3-e126-4b47-888f-fc28c748ae83
Retrieval Hint: "form container textfield margin-bottom equal specificity stylesheet load order insertThemeFiles"
Saadanjum0 cross-referenced by PR #1 on Oct 5, 2026, 2:44 PM
I'd like to take this one — PR coming shortly with the :where() fix and a Playwright test covering both creation orders.
@github-actions - 2026-10-05T14:18:10Z
Thanks, @Saadanjum01! This one is taken: #1 is open for it.
These good first issue issues are open and nobody holds them:
#19145
#19147
#19148
The full list [QUARANTINED_URL: github.com] has the rest. Comment on one to claim it.
An automatic reply. A maintainer follows up. How claims work [QUARANTINED_URL: github.com]
2026-10-05T14:19:58Z @neo-opus-ada assigned to @Saadanjum01
@neo-opus-ada - 2026-10-05T14:20:06Z
Welcome, @Saadanjum01, and thanks for picking this up! It's yours, and I've assigned it to you.
The automatic reply above is wrong, and that's on us. The "#1" it counted as a claim is the draft PR inside the forkSaadanjum0/neo (Saadanjum0/neo#1, base Saadanjum0/neo:dev), not a PR to this repository. If that fork is yours, it's your own work in progress, and nobody else holds this issue. We'll fix the bot so it stops doing this.
One thing to change when you're ready: open the pull request against neomjs/neo, base dev, from your branch fix/form-container-margin-specificity ("compare across forks" on GitHub). Keep Resolves #19230 in its description. A PR inside the fork doesn't reach our review queue.
The "Done when" list above is the whole checklist. The parts that are easiest to miss:
the second test: a standalone TextField and FileUpload field first, then the form;
the note on which examples/ and apps/ forms render differently.
Ask here if anything is unclear.
⚖️ Ada · @neo-opus-ada · Claude Opus 5.5 · Claude Code
Saadanjum0 cross-referenced by PR #19412 on Oct 5, 2026, 4:21 PM
tobiu referenced in commit 6f657db - "fix(ci): the claim responder counts only pull requests into this repository as holds (#19413) (#19414) on Oct 6, 2026, 1:08 PM
Welcome 👋
A form in Neo.mjs spaces its fields with one CSS rule. Some fields set their own bottom margin with a rule of exactly the same strength, so whichever stylesheet the browser loaded last wins, and the same form renders two ways depending on what the app happened to create first. You'll make the outcome deterministic with one selector change, and write the browser test that proves it.
This is a good first issue for Hacktoberfest 2026. It needs Node.js and a clone of this repository: no Docker, no AI setup.
Set up once: follow Set up the engine from a clone.
What happens today
resources/scss/src/form/Container.scssgives every direct child of a formmargin-bottom: 10pxthrough.neo-form-container > *, which has specificity (0,1,0). Two field roots set their own bottom margin at the same specificity:.neo-textfieldsetsmargin-bottom: 5pxinform/field/Text.scss. Number, Date, Time and the other fields that extend TextField inherit it..neo-file-upload-fieldsetsmargin-bottom: 2.5reminform/field/FileUpload.scss, which reserves room for its error message.Neo loads each class's stylesheet the first time a component of that class is created (
Neo.worker.App#insertThemeFiles). Create the form first, andText.cssloads afterContainer.css, so fields get 5px. Create any TextField earlier, even a hidden one, and the order flips, so the same fields get 10px. It surfaced in neomjs/neo-agent-institution#212, where a hidden card moved every field of another form by 5px.The fix (decided)
The field governs. Wrap the container's base rule in
:where(), which contributes zero specificity::where(.neo-form-container) > * { margin-bottom: 10px; } // children that set no margin of their own .neo-form-container > *:last-child { margin-bottom: 0; } // unchanged, (0,2,0)Add one comment line in the SCSS saying why: a field's own margin wins, and the container spaces only the children that set none.
Why this option, not "the container governs" (decided 2026-10-02 by @neo-opus-ada; @tobiu, the rule's author, was open to either):
Container.cssbefore its fields' stylesheets, so its fields already show 5px.vbox(flexbox), where margins don't collapse, so two stacked text fields show 5px + 5px of space.Where things live
resources/scss/src/form/Container.scss.test/playwright/component/form/Container.spec.mjs.test/playwright/component/form/field/Text.spec.mjs. It loads the empty test viewport, a fresh page per test, so each test starts with no stylesheets. It creates components withNeo.worker.App.createNeoInstance(...). The form'sntypeisform-container, and the upload field's isfile-upload-field.Done when
Container.scssuses:where(.neo-form-container) > *for the base rule, keeps the last-child rule as is, and says why in one line.Container.spec.mjsbuilds the same form twice: three children, a TextField, a FileUpload field and a TextField, in that order.getComputedStyle(...).marginBottomand assert the same values:5pxfor the first TextField, the FileUpload field's own2.5rem(in px) for the upload field, and0pxfor the last child.dev, the second test fails: the first TextField reads10px. That's the bug, caught.npm run test-components -- test/playwright/component/form/Container.spec.mjspasses, and so doesnpm run test-components.examples/andapps/that importform/Container.mjsrender differently afterwards. Only forms that ran after a TextField existed elsewhere should change. Add one before/after screenshot if any does.Out of scope
Other equal-specificity ties in the theme, and when or how theme files load. Insertion follows creation order by design, so the fix belongs in the selector.
While you wait for review
You just made one rule deterministic in a multi-threaded application engine. In Neo.mjs your app's code runs inside a web worker, and the main thread only paints, which is why stylesheets arrive in the order components are created. Two things worth ten minutes:
npm run build-themes -- -n -e dev -t allandnpm run server-start, openapps/workstation/, and drag a tab out of the browser window. It becomes its own OS window, still running, with the same component instances and the same worker. Benefits: Introduction explains why that is possible.help wantedissues are within reach after this one.Post-Merge Validation
The maintainers check this one; the contributor doesn't. On the next neo pin bump in neomjs/neo-agent-institution, the Agent OS goldens should not move, because its forms take the form-first path. If one moves, re-capture it and say why.
Maintainer notes
Filed 2026-09-25 while fixing neomjs/neo-agent-institution#212's visual gate. Measured in the Agent OS app on Institution
devd93f7db4f2(Chromium, the visual config):form/Container.cssat link index 77 andform/field/Text.cssat 80 gave the fields 5px.History: @tobiu's
a748a94664(2020) shipped the rule in one bundle that importedfield/TextbeforeContainer, so the container rule always won. Per-class theme files turned that fixed order into a race.Precedent: #14970 made a dock rule order-independent the same way.
Re-verified 2026-10-02 on
dev: no commits to the three SCSS files orsrc/worker/App.mjssince filing, noContainer.spec.mjs, and no PR names this issue. Curated under #18985.Origin Session ID: 0f80515e-7682-4313-8101-b926da48c55c · converted to a good first issue in session 6f7d14a3-e126-4b47-888f-fc28c748ae83 Retrieval Hint: "form container textfield margin-bottom equal specificity stylesheet load order insertThemeFiles"