Welcome 👋
Neo runs your app code in a web worker, which cannot touch the page's stylesheets. So Neo.util.Css forwards CSS rules to the main thread, where a Stylesheet addon inserts or deletes them. The helper is two static methods, and its test teaches Neo's most important idea: the app lives in a worker and asks the main thread to act. You stub the main-thread side and check what reaches 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.
Set up once: follow Set up the engine from a clone, including npm run bundle-browser-deps: without it the unit suite selects zero tests.
Where things live
- Source:
src/util/Css.mjs: static insertRules(windowId, rules) and static deleteRules(windowId, rules)
- New test file:
test/playwright/unit/util/Css.spec.mjs
- Pattern to copy:
test/playwright/unit/util/Array.spec.mjs (the setup() call and imports)
- Tip: in
test.beforeEach, keep what is there (const addon = Neo.ns('Neo.main.addon', true), original = addon.Stylesheet;) and set addon.Stylesheet = {insertCssRules(opts) {…}, deleteCssRules(opts) {…}} with functions that record what they receive. In test.afterEach, put the original back (original ? addon.Stylesheet = original : delete addon.Stylesheet). Never set it at the top of the file: test/playwright/unit/main/addon/Stylesheet.spec.mjs loads the real one, and npm run test-unit then stops with Namespace collision in unitTestMode.
What to cover
| Call |
The addon receives |
insertRules('w1', ['a {}', 'b {}']) |
insertCssRules({rules: ['a {}', 'b {}'], windowId: 'w1'}) |
insertRules('w1', 'a {}'), a single rule |
insertCssRules({rules: ['a {}'], windowId: 'w1'}): a single rule is wrapped in an array |
deleteRules('w1', ['a {}']) |
deleteCssRules({rules: ['a {}'], windowId: 'w1'}) |
deleteRules('w1', 'a {}') |
deleteCssRules({rules: ['a {}'], windowId: 'w1'}) |
Done when
Out of scope
The main-thread Stylesheet addon itself.
While you wait for review
You just pinned one small piece of a multi-threaded application engine: in Neo.mjs your app's code runs inside a web worker, and the main thread only paints. 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-agent-skills, 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.
Maintainer notes
Curated under #18985 (Hacktoberfest 2026). Found work: no unit or component spec names this module on dev (2026-09-23); the rows follow its two methods line by line.
Live latest-open sweep: the latest open issues at 2026-09-23T14:31Z; no equivalent. A2A: no claim on this scope. Memory Core: no prior decision.
Origin Session ID: 3be453e4-8b04-4865-be62-4cff34f4e0c6
Welcome 👋
Neo runs your app code in a web worker, which cannot touch the page's stylesheets. So
Neo.util.Cssforwards CSS rules to the main thread, where aStylesheetaddon inserts or deletes them. The helper is two static methods, and its test teaches Neo's most important idea: the app lives in a worker and asks the main thread to act. You stub the main-thread side and check what reaches 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, including
npm run bundle-browser-deps: without it the unit suite selects zero tests.Where things live
src/util/Css.mjs:static insertRules(windowId, rules)andstatic deleteRules(windowId, rules)test/playwright/unit/util/Css.spec.mjstest/playwright/unit/util/Array.spec.mjs(thesetup()call and imports)test.beforeEach, keep what is there (const addon = Neo.ns('Neo.main.addon', true), original = addon.Stylesheet;) and setaddon.Stylesheet = {insertCssRules(opts) {…}, deleteCssRules(opts) {…}}with functions that record what they receive. Intest.afterEach, put the original back (original ? addon.Stylesheet = original : delete addon.Stylesheet). Never set it at the top of the file:test/playwright/unit/main/addon/Stylesheet.spec.mjsloads the real one, andnpm run test-unitthen stops withNamespace collision in unitTestMode.What to cover
insertRules('w1', ['a {}', 'b {}'])insertCssRules({rules: ['a {}', 'b {}'], windowId: 'w1'})insertRules('w1', 'a {}'), a single ruleinsertCssRules({rules: ['a {}'], windowId: 'w1'}): a single rule is wrapped in an arraydeleteRules('w1', ['a {}'])deleteCssRules({rules: ['a {}'], windowId: 'w1'})deleteRules('w1', 'a {}')deleteCssRules({rules: ['a {}'], windowId: 'w1'})Done when
test/playwright/unit/util/Css.spec.mjsexists, following the pattern file's setup.test(...)per table row, and the stub is removed after the tests.src/.npm run test-unit -- test/playwright/unit/util/Css.spec.mjspasses.npm run test-unitpasses.Out of scope
The main-thread
Stylesheetaddon itself.While you wait for review
You just pinned one small piece of a multi-threaded application engine: in Neo.mjs your app's code runs inside a web worker, and the main thread only paints. 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.neo-agent-skills, and they need no Knowledge Base and no Memory Core. With Claude or GPT beside you, thehelp wantedissues are within reach after this one.Maintainer notes
Curated under #18985 (Hacktoberfest 2026). Found work: no unit or component spec names this module on
dev(2026-09-23); the rows follow its two methods line by line. Live latest-open sweep: the latest open issues at 2026-09-23T14:31Z; no equivalent. A2A: no claim on this scope. Memory Core: no prior decision.Origin Session ID: 3be453e4-8b04-4865-be62-4cff34f4e0c6