`;\n\nconst browser = await chromium.launch({ args: ['--js-flags=--expose-gc'] });\nconst page = await browser.newPage();\nawait page.setContent(html);\nconst names = ['asWritten', 'heldByRegistry', 'heldByDocumentListener', 'heldByTimer', 'heldByObserver', 'fixedHandle'];\nconst collected = await page.evaluate((n) => window.run(n), names);\nconsole.log('collected after el.remove(): ', JSON.stringify(collected));\nassert.deepEqual(collected, { asWritten: true, heldByRegistry: false, heldByDocumentListener: false, heldByTimer: false, heldByObserver: false, fixedHandle: true });\n\nconst released = await page.evaluate(() => window.release());\nconsole.log('after clearing each outside reference: ', JSON.stringify(released));\nassert.ok(Object.values(released).every(Boolean));\nawait browser.close();\n\nHow the harness reads: cases holds six functions. Each builds one widget a different way, removes its element, and keeps only a WeakRef to it. run calls the chosen cases, waits 50 ms, then calls gc() three times with 20 ms pauses. The first wait matters because a WeakRef target stays alive until the end of the task that created it, so the collection has to happen in a later task; repeating gc() gives the engine more than one chance to finish. refs[n].deref() === undefined is therefore true exactly when widget n was collected. release clears the registry, aborts the listener, clears the timer and disconnects the observer, then collects again.\n\nOutput:\n\ncollected after el.remove(): {\"asWritten\":true,\"heldByRegistry\":false,\"heldByDocumentListener\":false,\"heldByTimer\":false,\"heldByObserver\":false,\"fixedHandle\":true}\nafter clearing each outside reference: {\"asWritten\":true,\"heldByRegistry\":true,\"heldByDocumentListener\":true,\"heldByTimer\":true,\"heldByObserver\":true,\"fixedHandle\":true}\n\nIn the first line true means collected and false means still alive after removal. asWritten is the question's snippet, unchanged, and it is collected. The four held... widgets are retained until their outside reference is released, and fixedHandle is collected right after destroy(). The second line is all true because releasing every outside reference let the collector reclaim all six elements.\n\nPitfalls\n\nDo not add cleanup of listeners on the removed element itself out of habit. It costs code and hides the real holders.\nel.remove() is not destruction. It only detaches; whatever else points at the element decides whether it lives.\nSmall leaks multiply. One leaked widget with 10 child nodes is invisible; a list that re-renders 500 times a session leaks thousands of nodes.\nCollections hide leaks from reviewers. A Map of \"active widgets\" with no removal path is the most common real cause.\nComplexity: creation and destruction are O(1) per widget plus its subtree; the leak cost is O(widgets created x subtree size) of retained memory.\n\nRunning the code\n\nmkdir widget && cd widget # save test-widget.mjs here\necho '{\"type\":\"module\"}' > package.json\nnpm i playwright@1.48.2 && npx playwright install chromium\nnode test-widget.mjs\n\nRun in the mcr.microsoft.com/playwright:v1.48.2-jammy container (Node 20), where Chromium is already installed."}},{"@type":"Question","name":"Explain the BEM naming methodology and how it helps maintainable styles. Given this component markup, propose BEM-style class names and a simple CSS example for an input group with help text and an error state:\n
\n \n \n

Enter a value

\n
\n\nShow BEM classes and a modifier for an error state.","acceptedAnswer":{"@type":"Answer","text":"Direct answer\n\nBEM (Block, Element, Modifier) is a class-naming convention. A block is a standalone component (input-group), an element is a part that only makes sense inside its block (input-group__help, joined by two underscores), and a modifier is a variation or state of a block or element (input-group__help--error, joined by two hyphens). Every selector is a single class, so specificity (the ranking the cascade uses when two rules compete) is the same everywhere and a rule's meaning does not depend on where the markup sits. That makes styles easy to search, safe to move, and hard to break by accident.\n\nHow BEM helps maintainability\n\nFlat specificity. .input-group__input and .input-group__input--error both score (0,1,0): zero id selectors, one class, zero type selectors. Nothing needs !important or a longer selector to win; ordering in the file decides ties.\nNo dependence on DOM structure. .input-group__help works whether the paragraph is a child, a grandchild, or moved; compare .input-group > p, which breaks when someone wraps it.\nNamespacing by block name. A generic .help or .error in one feature will collide with another's. Prefixing with the block makes collisions unlikely without any tooling (it is still a convention, so nothing enforces it).\nGreppable and deletable. Searching input-group finds every rule and every use; deleting a component means deleting one block's rules.\nElements do not nest in names. Write card__title, never card__header__title; the element belongs to the block, not to another element.\n\nThe example: input group with help text and an error state\n\nThe original markup has two accessibility gaps worth fixing while adding classes: the