Learning Log

Continuity file. Append-only in spirit; prune only when something is genuinely resolved. Four sections: struggles, open questions, source readings, architecture decisions & revisits.


1. Concepts struggled with

Record the shape of the confusion, not just the topic. "Didn't get microtasks" is useless in three months. "Believed await yields to the event loop the way setTimeout does" is a diagnosis.

DateModuleConceptNature of the confusionResolved?How
2026-08-10fe-01Microtask checkpoint placementNatural but wrong model: "checkpoint runs at the end of each task." Predicts interleaving for setTimeout(() => el.click()), which does not happen.yesCorrect rule is stack-emptiness. Measured 4 dispatch paths.
2026-08-11fe-02Leak detection statisticsAssumed a clean workload would show R² near zero. It measured 0.742 — short series make noise look correlated. Slope alone and R² alone are both insufficient.yesReport slope AND linearity, plus a control run.
2026-08-11fe-02Harness isolationRan three listener variants on one page; variant 1's leak stayed alive through variants 2 and 3, so all three reported 300 detached nodes. JS-heap column stayed correct, making the table look partly plausible.yesFresh page per variant. Same class as fe-01's realm reuse.
2026-08-10fe-01Measurement trustTwo harness bugs produced clean, plausible, wrong data (realm reuse; per-event vs per-interaction INP).yesHarness now asserts liveness per trial; INP grouped by interactionId.

2. Open questions

Questions raised but not yet answered. A question that survives three modules is a signal — either it's genuinely hard, or the mental model underneath it is wrong.

#Raised inQuestionStatusAnswer / where it got resolved
1fe-03Why does PACKED_DOUBLE sum faster than PACKED_SMI (0.60x)? Accumulator-overflow hypothesis was tested and rejected (constraining values to i % 100 kept the sum in SMI range; ordering unchanged). Leading remaining hypothesis: per-add SMI overflow checks.open — deliberately not pursuedMagnitude is ~0.6ns/element. Settle with --print-opt-code if ever relevant.

3. Source-code readings

Per the spec's code-reading requirement and browser-framework-internals.md §44 (Source-Code Reading Ladder). Log what you read, what you were looking for, and what surprised you. The surprise column is the valuable one.

DateRepo / fileLooking forWhat I foundWhat surprised me
2026-08-10WHATWG HTML §8.1.7 (event loop)when a microtask checkpoint runs"when the JS execution context stack becomes empty" — not "at end of task"setTimeout(() => el.click()) is its own task yet still shows no checkpoint between listeners. The discriminator is stack emptiness, not task boundary.
2026-08-10Chrome 149, measuredrAF vs setTimeout(0) orderingnot a property of the primitives at allInput dispatch sits immediately before the rendering steps, so rAF is ~0ms away from a click handler and a whole task away from a plain script. Order flips 40/40 either way.

Verification log (browsers move; re-check anything older than ~6 months)

DateClaimVerdict
2026-08-11Flex/grid are slow; absolute positioning is fastFalse, measured — 1.4x spread across six layout modes over 20,000 items. Absolute was SLOWEST (75.5ms, 2.4x style cost from inline left/top); flex was fastest on relayout (7.3ms).
2026-08-11Container queries are expensiveFalse, measured — +1.4ms layout / +3.0ms style over 8,000 elements. The real cost is the containment constraint (container-type: inline-size implies size containment), not milliseconds.
2026-08-11Large DOM is inherently slow to renderFalse, measured — identical 261,011-node DOM took 401.9ms initial layout with no containment and 7.9ms with content-visibility: auto (−98%).
2026-08-11contain speeds up initial renderingFalse, measuredcontain: strict gave 398.4ms vs 401.9ms baseline. It bounds propagation (−80% on the update path), it does not skip rendering. Different tool, different problem.
2026-08-11Specificity decides the cascadeIncomplete, measured — layer order is evaluated first: 0,1,0 in a later layer beat 1,1,0 in an earlier one. Unlayered styles form an implicit final layer. Author !important inverts layer order.
2026-08-11minlength/maxlength apply to any valueFalse, measured — gated on the HTML spec's dirty value flag. el.value='short' reports valid; typing short reports tooShort. Tests that set .value pass while the real form rejects. Same class as fe-01's .click() finding.
2026-08-11Semantic HTML is more verbose than divsFalse, measured — div soup was 1.38x the markup of semantic HTML with 0 landmarks/headings/controls; div+ARIA 1.69x.
2026-08-11Property order is the main hidden-class hazardFalse, measured — alternating property order 1.14x; delete 11.93x. The folklore everyone repeats is minor; the practice everyone tolerates is the expensive one.
2026-08-11Megamorphic access is a performance crisisOverstated, measured — 1.9x ratio but only 2.9ns absolute. One forced layout = 1,448 megamorphic reads; one 200KB JSON.parse = 136,552.
2026-08-11V8 closures retain the whole Context, not just referenced variablesConfirmed Chrome 149 — an unused, never-called sibling closure caused 38.17MB retention vs 0.01MB. 5744x. V8 implementation detail, not a language guarantee.
2026-08-11performance.memory / Runtime.getHeapUsage can detect DOM leaksFalse, measured — freeing 507.8KB of detached DOM moved the JS heap by 0.10MB. DOM is in Blink's C++ heap.
2026-08-11Heap snapshots expose a per-node detachedness field (0/1/2)Confirmed Chrome 149 — exact detached counts without name-prefix matching.
2026-08-10await null costs 1 microtask tickConfirmed Chrome 149 — A1 P1 A2 P2 A3 P3. The widely-cited "3 ticks" is pre-2019 and stale.
2026-08-10Microtask chunking creates rendering opportunitiesFalse, measured — 0 rAF frames over 400 ms vs 48 for MessageChannel, identical structure.
2026-08-10scheduler.yield() available in ChromeConfirmed 149; same INP as MessageChannel but 3.4× wall clock in the 200k-row lab.
2026-08-10Workers reduce main-thread costConditional — naive worker was the worst strategy measured (1502 ms structured clone). True only when the data boundary is designed.

4. Architecture decisions & revisit queue

ADRs live in decisions/. This table is the index plus the revisit queue.

Decisions made

#DateDecisionReversibilityLink

Revisit queue

Topics marked revisit in PROGRESS.md, with the reason and the trigger for revisiting.

ModuleWhy flaggedRevisit trigger

5. Heuristics under test

The spec lists 10 Principal heuristics and requires counterexamples and limitations for each. Fill these in as you earn them — a heuristic with no counterexample is a slogan you haven't stress-tested.

HeuristicCounterexample foundLimitation
Prefer platform primitives when the browser already solves the problem.scheduler.yield() is the platform primitive and gave 3.4× the wall clock of a hand-rolled MessageChannel yield at identical INP (fe-01 lab)."Prefer" ≠ "always": the primitive optimises the common case (responsiveness), and you pay when your case differs (throughput).
Performance problems are often scheduling problems.Lab s5: pure serialisation cost (1502 ms structured clone), no scheduling error — the fix was a data-boundary decision, not a yield strategy.Says nothing about where the cost is. s2 and s5 had near-identical INP from completely different causes.
Move state to the lowest layer that actually owns it.
Optimize critical user journeys, not vanity benchmarks.Phase 1 produced three measured folklore corrections (fe-03 delete vs property order; fe-04 div soup 1.38x larger than semantic; fe-06 layout modes 1.4x with "fast" mode slowest). The reflex to build: when advice is repeated confidently and cheaply, measure before designing around it. Each had a real effect somewhere, misremembered as a general rule.
Every abstraction creates a future migration.
Test observable behavior, not implementation details.
E2E tests protect critical user journeys rather than duplicating unit tests.
Make invalid states difficult to represent.Applies to lifecycle, not just data: AbortController at registration beats removeEventListener because the teardown handle cannot be forgotten separately (fe-02). An anonymous listener is unrepairable after the fact.
Accessibility is architecture, not polish.
Performance problems are often scheduling problems.
Frontend architecture frequently reflects organizational architecture.