mithril-lynx 2.6.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/FETCH_INVESTIGATION.md +3 -2
  3. package/INPUT.md +115 -0
  4. package/LICENSE.txt +21 -0
  5. package/PLAN_REVIEW.md +134 -0
  6. package/README.md +47 -7
  7. package/REQUEST.md +5 -2
  8. package/ROUTE.md +111 -16
  9. package/ROUTE_CONTRACT_ANALYSIS.md +224 -0
  10. package/UPDATE_ANIMATION_GAP.md +133 -0
  11. package/informe-contrato-mithril-lynx.md +206 -0
  12. package/package.json +1 -9
  13. package/plugin.js +33 -0
  14. package/prompts-analisis-contrato-mithril-lynx.md +141 -0
  15. package/src/apply-patch.js +408 -68
  16. package/src/backends/virtual-backend.js +120 -22
  17. package/src/background.d.ts +40 -1
  18. package/src/background.js +116 -7
  19. package/src/channel.js +32 -7
  20. package/src/commit.js +9 -0
  21. package/src/dev-reload-client.js +46 -0
  22. package/src/dev-transport-noop.js +3 -0
  23. package/src/fake-dom.js +559 -61
  24. package/src/list-attributes.js +69 -0
  25. package/src/list-runtime.js +425 -0
  26. package/src/main-thread.js +33 -3
  27. package/src/mount-redraw.d.ts +10 -0
  28. package/src/mount-redraw.js +69 -3
  29. package/src/patch-protocol.js +52 -13
  30. package/src/reload/version.js +10 -1
  31. package/src/request.d.ts +2 -0
  32. package/src/request.js +59 -3
  33. package/src/route.d.ts +52 -5
  34. package/src/route.js +236 -59
  35. package/src/testing.d.ts +14 -1
  36. package/src/testing.js +79 -11
  37. package/test/event-render-coalescing.test.ts +117 -0
  38. package/test/gesture.test.ts +75 -0
  39. package/test/input.test.ts +388 -0
  40. package/test/list.test.ts +422 -100
  41. package/test/patch-protocol.test.ts +41 -0
  42. package/test/remove-event.test.ts +26 -0
  43. package/test/remove-subtree-cleanup.test.ts +75 -0
  44. package/test/route-hot-reload.test.ts +4 -2
  45. package/test/route.test.ts +206 -6
  46. package/test/setup.ts +10 -0
  47. package/test/style-clear.test.ts +97 -0
  48. package/.omo/plans/m-request-fetch-lynx.md +0 -306
  49. package/.omo/plans/m-route-en-memoria.md +0 -397
  50. package/.omo/plans/mithril-lynx-v2-desde-cero.md +0 -548
  51. package/.omo/run-continuation/ses_f48265d07ffenKG0GAz9ZRFvY6.json +0 -10
  52. package/src/list-cell.d.ts +0 -20
  53. package/src/list-cell.js +0 -73
  54. package/src/list-support.d.ts +0 -13
  55. package/src/list-support.js +0 -171
@@ -1,13 +0,0 @@
1
- // Ambient declaration for the ESM src/list-support.js — the main-thread
2
- // half of native list support. Not meant to be imported directly by app
3
- // code; apply-patch.js's own Op.CreateList case is the only caller. See
4
- // list-cell.d.ts for the background-thread half an app actually uses.
5
-
6
- export function createNativeList(
7
- pageId: number,
8
- scrollOrientation: string,
9
- listType: string,
10
- spanCount: number,
11
- createPatchApplier: (pageId: number, options?: { onEvent?: Function; flush?: boolean }) => object,
12
- onEvent?: Function,
13
- ): { handle: unknown; setCells: (cells: unknown[]) => void };
@@ -1,171 +0,0 @@
1
- // src/list-support.js
2
- //
3
- // Runs ONLY on the main thread — the counterpart to list-cell.js's
4
- // background-thread half. Wires up the native list contract
5
- // (__CreateList/__UpdateListCallbacks, the synchronous componentAtIndex/
6
- // enqueueComponent pair, recycling cells by a type key) — unchanged from
7
- // mithril-lynx v1's own device-verified list.js.
8
- //
9
- // What's different from v1, and from this file's own first version: a
10
- // cell's content is never rendered here. `componentAtIndex` only replays
11
- // ops the background thread already computed (list-cell.js), the same way
12
- // apply-patch.js's own top-level applyPatch replays the app's own tree —
13
- // this file has no dependency on mithril-runtime, fake-dom.js, or a virtual
14
- // backend at all anymore, because it never runs a render pass of its own.
15
- // componentAtIndex(itemCount) cost is therefore the cost of creating/
16
- // updating real elements from an already-known op list, same as any other
17
- // patch — not a render.
18
- //
19
- // Recycling a cell for a different item clears its real children (removed
20
- // by the handle ids list-cell.js recorded — see clearWrapperChildren below)
21
- // and replays the new item's ops fresh; this is a real teardown-and-rebuild
22
- // of that cell's content, not a diff against what was there before. A real
23
- // Mithril diff on recycle would need a persistent per-slot fake-dom
24
- // document kept in sync with which native cell native actually chose to
25
- // reuse — information only native has, on the main thread, which the
26
- // background thread (where the diff would have to run) can't see without a
27
- // round trip. Not implemented.
28
-
29
- export function createNativeList(pageId, scrollOrientation, listType, spanCount, createPatchApplier, onEvent) {
30
- let cells = []; // current cells, indexed by cellIndex — see patch-protocol.js's Op.SetListItems
31
- let count = 0;
32
- const signMap = new Map(); // sign -> { wrapperHandle, applier, rootChildIds, cellIndex, typeKey }
33
- const recycleMap = new Map(); // typeKey -> Map<sign, entry> (entries currently off-screen, available to reuse)
34
-
35
- function replayCell(wrapperHandle, cell) {
36
- const applier = createPatchApplier(pageId, { onEvent, flush: false });
37
- applier.registerRoot(cell.containerId, wrapperHandle);
38
- applier.applyPatch(cell.ops);
39
- return applier;
40
- }
41
-
42
- function clearWrapperChildren(entry) {
43
- for (const id of entry.rootChildIds) {
44
- const handle = entry.applier.getHandle(id);
45
- if (handle != null) __RemoveElement(entry.wrapperHandle, handle);
46
- }
47
- }
48
-
49
- function bindFreshItem(listHandle, listId, cellIndex, opId, cell, flush) {
50
- const wrapperHandle = __CreateElement("list-item", pageId, {});
51
- __SetAttribute(wrapperHandle, "item-key", String(cellIndex));
52
- __AppendElement(listHandle, wrapperHandle);
53
-
54
- const applier = replayCell(wrapperHandle, cell);
55
- const sign = __GetElementUniqueID(wrapperHandle);
56
- signMap.set(sign, { wrapperHandle, applier, rootChildIds: cell.rootChildIds, cellIndex, typeKey: cell.typeKey });
57
- if (flush) __FlushElementTree(wrapperHandle, { triggerLayout: true, operationID: opId, elementID: sign, listID: listId });
58
- return sign;
59
- }
60
-
61
- function bindRecycledItem(listId, cellIndex, opId, cell, pool, flush) {
62
- const [sign, entry] = pool.entries().next().value;
63
- pool.delete(sign);
64
- clearWrapperChildren(entry);
65
- __SetAttribute(entry.wrapperHandle, "item-key", String(cellIndex));
66
- const applier = replayCell(entry.wrapperHandle, cell);
67
- entry.applier = applier;
68
- entry.rootChildIds = cell.rootChildIds;
69
- entry.cellIndex = cellIndex;
70
- signMap.set(sign, entry);
71
- if (flush) __FlushElementTree(entry.wrapperHandle, { triggerLayout: true, operationID: opId, elementID: sign, listID: listId });
72
- return sign;
73
- }
74
-
75
- function bindCell(listHandle, listId, cellIndex, opId, flush) {
76
- if (cellIndex < 0 || cellIndex >= count) {
77
- throw new Error(`[mithril-lynx] list: cellIndex ${cellIndex} out of range (itemCount=${count})`);
78
- }
79
- const cell = cells[cellIndex];
80
- const pool = recycleMap.get(cell.typeKey);
81
- if (pool && pool.size > 0) return bindRecycledItem(listId, cellIndex, opId, cell, pool, flush);
82
- return bindFreshItem(listHandle, listId, cellIndex, opId, cell, flush);
83
- }
84
-
85
- function componentAtIndex(listHandle, listId, cellIndex, opId) {
86
- return bindCell(listHandle, listId, cellIndex, opId, true);
87
- }
88
-
89
- /**
90
- * The batched form of componentAtIndex — real native calls this (not a
91
- * fallback: confirmed against `@lynx-js/react`'s own
92
- * componentAtIndexFactory/attachListItemAtIndex, which always registers
93
- * BOTH forms) for several cells at once, expecting exactly ONE
94
- * `__FlushElementTree` covering all of them (`operationIDs`/`elementIDs`
95
- * arrays), not one per cell — the real reason a native list's initial,
96
- * simultaneously-visible cells need this at all.
97
- */
98
- function componentAtIndexes(listHandle, listId, cellIndexes, operationIDs) {
99
- const elementIDs = cellIndexes.map((cellIndex, i) => bindCell(listHandle, listId, cellIndex, operationIDs[i], false));
100
- __FlushElementTree(listHandle, { triggerLayout: true, operationIDs, elementIDs, listID: listId });
101
- }
102
-
103
- function enqueueComponent(_listHandle, _listId, sign) {
104
- const entry = signMap.get(sign);
105
- if (entry == null) return;
106
- signMap.delete(sign);
107
- if (!recycleMap.has(entry.typeKey)) recycleMap.set(entry.typeKey, new Map());
108
- recycleMap.get(entry.typeKey).set(sign, entry);
109
- }
110
-
111
- function sendListInfo(listHandle, listId, insertAction, removeAction, updateAction) {
112
- __SetAttribute(listHandle, "update-list-info", { insertAction, removeAction, updateAction });
113
- __UpdateListCallbacks(listHandle, componentAtIndex, enqueueComponent, componentAtIndexes);
114
- }
115
-
116
- const listHandle = __CreateList(pageId, componentAtIndex, enqueueComponent, {}, componentAtIndexes);
117
- const listId = __GetElementUniqueID(listHandle);
118
- __SetAttribute(listHandle, "scroll-orientation", scrollOrientation);
119
- __SetAttribute(listHandle, "list-type", listType);
120
- __SetAttribute(listHandle, "span-count", String(spanCount));
121
-
122
- // Re-flushes every currently-attached (on-screen) cell with its newest
123
- // ops — e.g. after a redraw triggered by a tap inside a cell, or any
124
- // other app state change that reaches this list's items. Unconditional,
125
- // same as List's own onupdate already unconditionally resends the whole
126
- // `items` array on every relevant redraw (see mithril-lynx-ui's
127
- // list/list.js) — a real "did this cell's content actually change"
128
- // check would need to compare `ops` arrays, not implemented yet. Cells
129
- // that aren't attached right now just pick up their new content next
130
- // time componentAtIndex asks for that index.
131
- function refreshAttachedCells(nextCells) {
132
- for (const [sign, entry] of signMap) {
133
- const nextCell = nextCells[entry.cellIndex];
134
- clearWrapperChildren(entry);
135
- entry.applier = replayCell(entry.wrapperHandle, nextCell);
136
- entry.rootChildIds = nextCell.rootChildIds;
137
- entry.typeKey = nextCell.typeKey;
138
- signMap.set(sign, entry);
139
- __FlushElementTree(entry.wrapperHandle, { triggerLayout: false });
140
- }
141
- }
142
-
143
- function setCells(nextCells) {
144
- const nextCount = nextCells.length;
145
- refreshAttachedCells(nextCells);
146
- cells = nextCells;
147
- if (nextCount === count) return;
148
- if (nextCount > count) {
149
- sendListInfo(
150
- listHandle,
151
- listId,
152
- Array.from({ length: nextCount - count }, (_, i) => ({ position: count + i, type: "cell", "item-key": String(count + i) })),
153
- [],
154
- [],
155
- );
156
- } else {
157
- sendListInfo(listHandle, listId, [], Array.from({ length: count - nextCount }, (_, i) => nextCount + i), []);
158
- }
159
- count = nextCount;
160
- }
161
-
162
- // Returned separately, not as a property stuck onto `listHandle` itself:
163
- // confirmed on real hardware (unlike @lynx-js/testing-environment's
164
- // simulated PAPI) that a native list's own returned handle does not
165
- // reliably hold a custom property across calls — assigning one there
166
- // and reading it back later from apply-patch.js's Op.SetListItems case
167
- // threw "TypeError: not a function" on the very first SetListItems, even
168
- // with zero items. apply-patch.js keeps `setCells` in its own map
169
- // instead, keyed the same way `handles` already is.
170
- return { handle: listHandle, setCells };
171
- }