mithril-lynx 2.0.0 → 2.0.2
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.
- package/ANDROID_APK_GUIDE.md +1152 -0
- package/FETCH_INVESTIGATION.md +2 -2
- package/README.md +9 -0
- package/package.json +1 -1
- package/plugin.d.ts +2 -2
- package/plugin.js +36 -35
- package/src/apply-patch.js +4 -4
- package/src/channel.js +2 -2
- package/src/commit.js +10 -10
- package/src/dev-reload-client.js +6 -6
- package/src/fake-dom.js +4 -4
- package/src/mount-redraw.js +1 -1
- package/src/request.js +2 -2
- package/src/route.js +2 -2
- package/test/end-to-end.test.ts +2 -2
- package/test/route-hot-reload.test.ts +1 -1
- package/test/route.test.ts +1 -1
- package/test/setup.ts +3 -3
- package/test/structural-reload.test.ts +3 -3
package/FETCH_INVESTIGATION.md
CHANGED
|
@@ -70,7 +70,7 @@ y los `.d.ts` reales instalados en
|
|
|
70
70
|
`fetch` está declarado como método del objeto `Lynx` dentro de
|
|
71
71
|
`types/background-thread/` — **solo existe en el hilo background**, no
|
|
72
72
|
en `common/` ni `main-thread/`. Encaja con la arquitectura entera de
|
|
73
|
-
`mithril-lynx
|
|
73
|
+
`mithril-lynx` (toda la vista corre en background) — no hace falta
|
|
74
74
|
ningún puente cross-thread para esto.
|
|
75
75
|
|
|
76
76
|
### 2.2 La firma real, completa, sin recortar
|
|
@@ -290,7 +290,7 @@ cancelación y timeout reales, que en la primera pasada de esta
|
|
|
290
290
|
investigación parecían imposibles — funciona.
|
|
291
291
|
|
|
292
292
|
**Recomendación**: implementar el subconjunto confirmado como
|
|
293
|
-
`mithril-lynx
|
|
293
|
+
`mithril-lynx/request`, documentando explícitamente (no escondiendo)
|
|
294
294
|
los 4-5 puntos sin equivalente, con una sugerencia directa de usar
|
|
295
295
|
`lynx.fetch` nativo para esos casos puntuales.
|
|
296
296
|
|
package/README.md
CHANGED
|
@@ -53,6 +53,14 @@ renderApp({ root: () => m(Counter) });
|
|
|
53
53
|
|
|
54
54
|
`m.request`, reimplemented as a wrapper over Lynx's own `fetch`. See [`REQUEST.md`](./REQUEST.md) for the full API, and [`FETCH_INVESTIGATION.md`](./FETCH_INVESTIGATION.md) for the complete option-by-option gap analysis against the real `m.request` spec, backed by real-device evidence rather than docs/types alone (which were wrong twice during that investigation).
|
|
55
55
|
|
|
56
|
+
## Custom fonts
|
|
57
|
+
|
|
58
|
+
Use a plain CSS `@font-face` rule — not `lynx.addFont()` (that JS API only fires post-mount, too late to win the first-frame race). Three gotchas, all confirmed on real hardware and inherited unchanged from the previous mithril-lynx (none of this is architecture-specific):
|
|
59
|
+
|
|
60
|
+
- **The font file must be `.ttf`, not `.woff2`** — a `.woff2` `@font-face` compiles fine but the native text renderer silently never applies it.
|
|
61
|
+
- **`font-family` set on `:root` (or any ancestor) does not cascade to descendants by default** — `pluginLynxConfig({ enableCSSInheritance: true })` turns that on.
|
|
62
|
+
- **A declarative `@font-face` resolves synchronously on the first native `__FlushElementTree()` call**, and that cost scales with how many text nodes resolve it — up to +2s of cold start on a mid/low-end device. Filed upstream as [lynx-family/lynx#9431](https://github.com/lynx-family/lynx/issues/9431). The workaround is a native-side prefetch hook, not a JS-level fix — see [`ANDROID_APK_GUIDE.md`](./ANDROID_APK_GUIDE.md) Part D for the full procedure, or scaffold it directly with `create-mithril-lynx`'s `--with-font <file.ttf>` flag.
|
|
63
|
+
|
|
56
64
|
## Known gaps
|
|
57
65
|
|
|
58
66
|
- **`m.trust`** — not present. Stripped from `mithril-runtime` at the source, and Lynx's Element PAPI has no innerHTML-equivalent injection point to reimplement it against anyway (same permanent gap v1 documented).
|
|
@@ -72,3 +80,4 @@ Runs against `@lynx-js/testing-environment`'s real Element PAPI simulation via `
|
|
|
72
80
|
- `.omo/plans/m-route-en-memoria.md` — how `m.route` was designed and verified for an in-memory, URL-less environment.
|
|
73
81
|
- `.omo/plans/m-request-fetch-lynx.md` — the `m.request`-vs-`fetch` investigation plan and its execution log.
|
|
74
82
|
- [`ROUTE.md`](./ROUTE.md), [`REQUEST.md`](./REQUEST.md), [`FETCH_INVESTIGATION.md`](./FETCH_INVESTIGATION.md) — user-facing reference docs for the two Lynx-specific reimplementations.
|
|
83
|
+
- [`ANDROID_APK_GUIDE.md`](./ANDROID_APK_GUIDE.md) — building a native Android host and APK from scratch, Gradle-CLI only, including the `.ttf` cold-start hack from "Custom fonts" above. Automated end to end by `create-mithril-lynx --android`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mithril-lynx",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "Mithril.js on Lynx: real mithril/render/render.js driven through a Lynx-backed fake DOM, with an explicit single commit hook (no conditional global flush) and three reload modes (data-light, structural-light, full). A complete rewrite of the previous mithril-lynx (0.0.x).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/plugin.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { RsbuildPlugin } from "@lynx-js/rspeedy";
|
|
2
2
|
|
|
3
|
-
export interface
|
|
3
|
+
export interface PluginMithrilLynxOptions {
|
|
4
4
|
targetSdkVersion?: string;
|
|
5
5
|
liveReload?: boolean;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
export function
|
|
8
|
+
export function pluginMithrilLynx(options?: PluginMithrilLynxOptions): RsbuildPlugin;
|
package/plugin.js
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
// plugin.js
|
|
2
2
|
//
|
|
3
3
|
// Rspeedy/Rsbuild plugin wiring the two-bundle build (main-thread/Lepus +
|
|
4
|
-
// background/JS) for mithril-lynx
|
|
5
|
-
// plugin.js — this file is build TOOLING, not the
|
|
6
|
-
// that motivated the
|
|
7
|
-
//
|
|
8
|
-
//
|
|
4
|
+
// background/JS) for mithril-lynx apps. Adapted from the previous
|
|
5
|
+
// mithril-lynx's plugin.js — this file is build TOOLING, not the
|
|
6
|
+
// redraw/reload mechanism that motivated the rewrite (see
|
|
7
|
+
// mithril-lynx-v2-desde-cero.md §2: the old bugs lived in the
|
|
8
|
+
// shim/commit/reload layer, never here), so it is reused with fixes
|
|
9
|
+
// rather than rewritten from nothing. Two real changes from the old one:
|
|
9
10
|
//
|
|
10
11
|
// 1. (F0.2 fix, the actual point of this file's existence in the plan)
|
|
11
12
|
// `RuntimeWrapperWebpackPlugin`'s `test` regex now also matches
|
|
12
|
-
// `.hot-update.js` chunks.
|
|
13
|
+
// `.hot-update.js` chunks. The old regex (`${name}/background\.js$`)
|
|
13
14
|
// matched the initial background ASSET path (nested under
|
|
14
15
|
// `.rspeedy/<name>/`, with a slash) but never the flat, double-
|
|
15
16
|
// underscore-named hot-update chunk (`<name>__background.<hash>.hot-
|
|
16
17
|
// update.js`) — confirmed with a plain regex test against both real
|
|
17
|
-
// filenames, not a guess. That gap is the entire reason
|
|
18
|
-
// runtime monkey-patch of `lynx.requireModuleAsync`
|
|
19
|
-
// client;
|
|
18
|
+
// filenames, not a guess. That gap is the entire reason the old
|
|
19
|
+
// version needed a runtime monkey-patch of `lynx.requireModuleAsync`
|
|
20
|
+
// in its dev-reload client; this one's client has no such patch (see
|
|
21
|
+
// src/dev-reload-client.js).
|
|
20
22
|
//
|
|
21
|
-
// 2. Only ONE rendering mode exists (
|
|
23
|
+
// 2. Only ONE rendering mode exists (plan §2 non-goals: no main-thread-
|
|
22
24
|
// owned/data-channel modes) — so there is no mode-detection logic here,
|
|
23
25
|
// `dev.hmr` is unconditionally on in dev, and every entry always gets a
|
|
24
26
|
// background chunk.
|
|
@@ -31,7 +33,7 @@ import { fileURLToPath } from "node:url";
|
|
|
31
33
|
import { RuntimeWrapperWebpackPlugin } from "@lynx-js/runtime-wrapper-webpack-plugin";
|
|
32
34
|
import { LynxEncodePlugin, LynxTemplatePlugin } from "@lynx-js/template-webpack-plugin";
|
|
33
35
|
|
|
34
|
-
const PLUGIN_NAME = "mithril-lynx-
|
|
36
|
+
const PLUGIN_NAME = "mithril-lynx-template-webpack";
|
|
35
37
|
const STYLE_CANDIDATES = ["style.css"];
|
|
36
38
|
|
|
37
39
|
const DEV_RELOAD_CLIENT_PATH = path.join(
|
|
@@ -105,7 +107,7 @@ function packageRootOf(resolvedFile, expectedName) {
|
|
|
105
107
|
return null;
|
|
106
108
|
}
|
|
107
109
|
|
|
108
|
-
export function
|
|
110
|
+
export function pluginMithrilLynx(options = {}) {
|
|
109
111
|
const targetSdkVersion = options.targetSdkVersion ?? "3.5";
|
|
110
112
|
const liveReload = options.liveReload ?? true;
|
|
111
113
|
|
|
@@ -114,8 +116,9 @@ export function pluginMithrilLynxV2(options = {}) {
|
|
|
114
116
|
setup(api) {
|
|
115
117
|
api.expose(Symbol.for("LynxTemplatePlugin"), { LynxTemplatePlugin });
|
|
116
118
|
|
|
117
|
-
// One mode only -> dev.hmr is unconditionally on in dev (
|
|
118
|
-
// detect renderer-mode-vs-not here;
|
|
119
|
+
// One mode only -> dev.hmr is unconditionally on in dev (the old
|
|
120
|
+
// version had to detect renderer-mode-vs-not here; there is no
|
|
121
|
+
// "not" to detect anymore).
|
|
119
122
|
api.modifyRsbuildConfig({
|
|
120
123
|
handler: (config, { mergeRsbuildConfig }) => mergeRsbuildConfig(config, { dev: { hmr: true } }),
|
|
121
124
|
order: "post",
|
|
@@ -134,13 +137,12 @@ export function pluginMithrilLynxV2(options = {}) {
|
|
|
134
137
|
});
|
|
135
138
|
|
|
136
139
|
api.modifyBundlerChain((chain, { isDev, environment }) => {
|
|
137
|
-
// Force a single resolved copy of "mithril-runtime"
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
// mithril's emptyAttrs singleton, and
|
|
142
|
-
// per-app render state
|
|
143
|
-
// comments for the on-device symptom).
|
|
140
|
+
// Force a single resolved copy of "mithril-runtime" — a
|
|
141
|
+
// `file:`-linked local package can otherwise resolve a
|
|
142
|
+
// second physical copy with its own module-level state
|
|
143
|
+
// (this exact class of bug bit the previous mithril-lynx:
|
|
144
|
+
// mithril's own emptyAttrs singleton, and that framework's
|
|
145
|
+
// own per-app render state).
|
|
144
146
|
try {
|
|
145
147
|
const appRequire = createRequire(path.join(process.cwd(), "package.json"));
|
|
146
148
|
const mithrilDir = path.dirname(appRequire.resolve("mithril-runtime/package.json"));
|
|
@@ -148,17 +150,16 @@ export function pluginMithrilLynxV2(options = {}) {
|
|
|
148
150
|
} catch {
|
|
149
151
|
// App has no local "mithril-runtime" resolvable from its own root.
|
|
150
152
|
}
|
|
151
|
-
// Note:
|
|
152
|
-
// second copy
|
|
153
|
-
// renderers with separate module-level state
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
// practice.
|
|
153
|
+
// Note: the old mithril-lynx also force-aliased its OWN
|
|
154
|
+
// package name here (a second copy would mean two
|
|
155
|
+
// disconnected renderers with separate module-level state).
|
|
156
|
+
// This framework's per-app state lives inside closures
|
|
157
|
+
// created by `renderApp()`/`setupRenderer()` calls, not
|
|
158
|
+
// module-level variables — the same class of bug can't
|
|
159
|
+
// reappear the same way, so this dedup isn't reproduced
|
|
160
|
+
// here. Revisit if a multi-copy scenario (npm link, a
|
|
161
|
+
// component library nesting its own copy) turns up the same
|
|
162
|
+
// symptom in practice.
|
|
162
163
|
|
|
163
164
|
const rawEntries = Object.entries(chain.entryPoints.entries() ?? {});
|
|
164
165
|
chain.entryPoints.clear();
|
|
@@ -174,8 +175,8 @@ export function pluginMithrilLynxV2(options = {}) {
|
|
|
174
175
|
const cssSource = findSibling(dir, STYLE_CANDIDATES);
|
|
175
176
|
if (bgSource == null) {
|
|
176
177
|
throw new Error(
|
|
177
|
-
`[mithril-lynx
|
|
178
|
-
"mithril-lynx
|
|
178
|
+
`[mithril-lynx] entry "${name}": no sibling background.ts/background.js found next to ${mtSource}. ` +
|
|
179
|
+
"mithril-lynx has exactly one rendering mode and it always needs a background entry — see the plan's §2 non-goals.",
|
|
179
180
|
);
|
|
180
181
|
}
|
|
181
182
|
|
|
@@ -231,7 +232,7 @@ export function pluginMithrilLynxV2(options = {}) {
|
|
|
231
232
|
);
|
|
232
233
|
|
|
233
234
|
console.info(
|
|
234
|
-
`[mithril-lynx
|
|
235
|
+
`[mithril-lynx:build] entry="${name}" hmr=true liveReload=${liveReload} ` +
|
|
235
236
|
`bgEntry=${bgEntry} mtEntry=${mtEntry} targetSdk=${targetSdkVersion}`,
|
|
236
237
|
);
|
|
237
238
|
}
|
package/src/apply-patch.js
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
// content) is not a guess — it's the same contract `mithril-lynx/CONTRACT.md`
|
|
14
14
|
// + `mithril-lynx/src/lynx-mithril-shim.js` already validated on a real
|
|
15
15
|
// device (see mithril-lynx/DEVICE_VERIFICATION.md). Reusing a validated
|
|
16
|
-
// mapping here is exactly the kind of "concept, not code" reuse the
|
|
17
|
-
// allows (§2 non-goals) — the bug
|
|
16
|
+
// mapping here is exactly the kind of "concept, not code" reuse the plan
|
|
17
|
+
// allows (§2 non-goals) — the bug this rewrite fixes lives in the
|
|
18
18
|
// commit/reload layer (commit.js, reload/*.js), never in this mapping.
|
|
19
19
|
|
|
20
20
|
import { Op } from "./patch-protocol.js";
|
|
@@ -138,7 +138,7 @@ export function createPatchApplier(pageId, { onEvent } = {}) {
|
|
|
138
138
|
// as a test failure rather than a mystery on-device.
|
|
139
139
|
if (name === "*") {
|
|
140
140
|
throw new Error(
|
|
141
|
-
"[mithril-lynx
|
|
141
|
+
"[mithril-lynx] Clearing the whole `style` object at once is not implemented yet (F3 TODO) — set individual properties to \"\" instead.",
|
|
142
142
|
);
|
|
143
143
|
}
|
|
144
144
|
__AddInlineStyle(handles.get(id), name, "");
|
|
@@ -169,7 +169,7 @@ export function createPatchApplier(pageId, { onEvent } = {}) {
|
|
|
169
169
|
break;
|
|
170
170
|
}
|
|
171
171
|
default:
|
|
172
|
-
throw new Error(`[mithril-lynx
|
|
172
|
+
throw new Error(`[mithril-lynx] Unknown patch opcode: ${opcode}`);
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
__FlushElementTree();
|
package/src/channel.js
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
// v1's renderer/background.js and renderer/main-thread.js dispatching to
|
|
16
16
|
// and listening on each other via exactly this pairing.
|
|
17
17
|
|
|
18
|
-
export const patchEventName = "
|
|
19
|
-
export const eventFromMainThreadEventName = "
|
|
18
|
+
export const patchEventName = "MithrilLynx:Patch";
|
|
19
|
+
export const eventFromMainThreadEventName = "MithrilLynx:Event";
|
|
20
20
|
export const renderPageEventName = "__RenderPage";
|
|
21
21
|
export const destroyLifetimeEventName = "__DestroyLifetime";
|
|
22
22
|
|
package/src/commit.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
// src/commit.js
|
|
2
2
|
//
|
|
3
3
|
// The single, explicit, non-conditional flush contract — this is the actual
|
|
4
|
-
// fix for the regression that motivated the whole
|
|
4
|
+
// fix for the regression that motivated the whole rewrite (see
|
|
5
5
|
// mithril-lynx-v2-desde-cero.md §3.4 and mithril-lynx/AGENTS.md's "Estado
|
|
6
|
-
// actual" section for the
|
|
6
|
+
// actual" section for the old implementation's postmortem).
|
|
7
7
|
//
|
|
8
|
-
//
|
|
8
|
+
// The old bug in one sentence: whether a redraw actually reached the main
|
|
9
9
|
// thread depended on `typeof globalThis.__FlushElementTree === "function"`
|
|
10
10
|
// — a question whose answer depended on thread/test/mode ordering. That is
|
|
11
11
|
// a race condition baked into the architecture, not an edge case to patch.
|
|
12
12
|
//
|
|
13
|
-
//
|
|
14
|
-
// `renderApp()` call (see background.js). It is installed explicitly,
|
|
15
|
-
// by the code that owns the render — never discovered implicitly by
|
|
13
|
+
// The rule here: there is exactly one commit callback for the lifetime of
|
|
14
|
+
// one `renderApp()` call (see background.js). It is installed explicitly,
|
|
15
|
+
// once, by the code that owns the render — never discovered implicitly by
|
|
16
16
|
// whoever happens to ask first. Asking to commit before installing one is a
|
|
17
17
|
// programmer error and throws immediately and loudly, on the same tick,
|
|
18
18
|
// with a message that says exactly what's missing — never a silently
|
|
19
|
-
// frozen screen (which is what
|
|
19
|
+
// frozen screen (which is what the old implementation did instead).
|
|
20
20
|
|
|
21
|
-
const NOT_MOUNTED = Symbol("mithril-lynx
|
|
21
|
+
const NOT_MOUNTED = Symbol("mithril-lynx:not-mounted");
|
|
22
22
|
|
|
23
23
|
export function createCommitController() {
|
|
24
24
|
let commitFn = NOT_MOUNTED;
|
|
@@ -33,7 +33,7 @@ export function createCommitController() {
|
|
|
33
33
|
install(fn) {
|
|
34
34
|
if (commitFn !== NOT_MOUNTED) {
|
|
35
35
|
throw new Error(
|
|
36
|
-
"[mithril-lynx
|
|
36
|
+
"[mithril-lynx] commit callback already installed. " +
|
|
37
37
|
"A shim instance is single-use: one renderApp() call, one " +
|
|
38
38
|
"commit callback, for the lifetime of that background " +
|
|
39
39
|
"context. If you're re-mounting for a reload, create a new " +
|
|
@@ -55,7 +55,7 @@ export function createCommitController() {
|
|
|
55
55
|
commit() {
|
|
56
56
|
if (commitFn === NOT_MOUNTED) {
|
|
57
57
|
throw new Error(
|
|
58
|
-
"[mithril-lynx
|
|
58
|
+
"[mithril-lynx] commit() called before renderApp() mounted " +
|
|
59
59
|
"the app. This is always a bug in the framework's own " +
|
|
60
60
|
"wiring, never something app code can trigger by accident " +
|
|
61
61
|
"— app code never calls commit() directly.",
|
package/src/dev-reload-client.js
CHANGED
|
@@ -114,7 +114,7 @@ function invokeCdpReload() {
|
|
|
114
114
|
if (!data) return;
|
|
115
115
|
try {
|
|
116
116
|
var parsed = JSON.parse(data);
|
|
117
|
-
if (parsed.error) console.error("[mithril-lynx
|
|
117
|
+
if (parsed.error) console.error("[mithril-lynx] Page.reload failed:", parsed.error.message);
|
|
118
118
|
} catch (e) {
|
|
119
119
|
// response is not JSON — ignore
|
|
120
120
|
}
|
|
@@ -129,7 +129,7 @@ function reload(reason) {
|
|
|
129
129
|
if (socket) socket.close();
|
|
130
130
|
if (!invokeCdpReload()) {
|
|
131
131
|
console.warn(
|
|
132
|
-
"[mithril-lynx
|
|
132
|
+
"[mithril-lynx] Live reload unavailable: NativeModules.LynxDevToolSetModule.invokeCdp was not found.",
|
|
133
133
|
);
|
|
134
134
|
}
|
|
135
135
|
}
|
|
@@ -191,7 +191,7 @@ function handleMessage(rawMessage) {
|
|
|
191
191
|
try {
|
|
192
192
|
message = JSON.parse(rawMessage);
|
|
193
193
|
} catch (e) {
|
|
194
|
-
console.warn("[mithril-lynx
|
|
194
|
+
console.warn("[mithril-lynx] Ignoring an invalid dev-server message.");
|
|
195
195
|
return;
|
|
196
196
|
}
|
|
197
197
|
|
|
@@ -219,7 +219,7 @@ function handleMessage(rawMessage) {
|
|
|
219
219
|
}
|
|
220
220
|
break;
|
|
221
221
|
case "errors":
|
|
222
|
-
console.warn("[mithril-lynx
|
|
222
|
+
console.warn("[mithril-lynx] Build failed; waiting for the next successful build.", message.data);
|
|
223
223
|
break;
|
|
224
224
|
}
|
|
225
225
|
}
|
|
@@ -230,11 +230,11 @@ function connect(retries) {
|
|
|
230
230
|
|
|
231
231
|
socket = new WebSocket(socketURL(options));
|
|
232
232
|
socket.onmessage = function (event) { handleMessage(event.data); };
|
|
233
|
-
socket.onerror = function (error) { console.error("[mithril-lynx
|
|
233
|
+
socket.onerror = function (error) { console.error("[mithril-lynx] Dev server connection error:", error); };
|
|
234
234
|
socket.onclose = function () {
|
|
235
235
|
if (reloading) return;
|
|
236
236
|
if (retries >= 10) {
|
|
237
|
-
console.error("[mithril-lynx
|
|
237
|
+
console.error("[mithril-lynx] Unable to reconnect to the dev server.");
|
|
238
238
|
return;
|
|
239
239
|
}
|
|
240
240
|
var delay = 1000 * Math.pow(2, retries) + Math.random() * 100;
|
package/src/fake-dom.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// Mithril's render.js at all — it only replays the recorded ops through
|
|
22
22
|
// `apply-patch.js`, which calls the real Element PAPI directly. That split
|
|
23
23
|
// is the point of the whole architecture (see
|
|
24
|
-
//
|
|
24
|
+
// .omo/plans/mithril-lynx-v2-desde-cero.md §3.1): only ONE
|
|
25
25
|
// side needs to be "a DOM", the other side only needs to be "a PAPI patch
|
|
26
26
|
// applier".
|
|
27
27
|
|
|
@@ -186,7 +186,7 @@ export class LynxElement extends LynxContainerNode {
|
|
|
186
186
|
// CSS-text parser. Documented limitation, not a silent bug.
|
|
187
187
|
if (typeof console !== "undefined") {
|
|
188
188
|
console.warn(
|
|
189
|
-
"[mithril-lynx
|
|
189
|
+
"[mithril-lynx] Assigning a CSS text string to `style` is not supported; use a style object.",
|
|
190
190
|
);
|
|
191
191
|
}
|
|
192
192
|
return;
|
|
@@ -265,7 +265,7 @@ export class LynxElement extends LynxContainerNode {
|
|
|
265
265
|
// already-empty-or-not container.
|
|
266
266
|
if (value !== "") {
|
|
267
267
|
if (typeof console !== "undefined") {
|
|
268
|
-
console.warn("[mithril-lynx
|
|
268
|
+
console.warn("[mithril-lynx] Non-empty `textContent` assignment is not supported.");
|
|
269
269
|
}
|
|
270
270
|
return;
|
|
271
271
|
}
|
|
@@ -280,7 +280,7 @@ export class LynxElement extends LynxContainerNode {
|
|
|
280
280
|
// mithril-lynx/AGENTS.md history) rather than silently doing nothing
|
|
281
281
|
// with no signal.
|
|
282
282
|
if (typeof console !== "undefined") {
|
|
283
|
-
console.warn("[mithril-lynx
|
|
283
|
+
console.warn("[mithril-lynx] `m.trust()` / innerHTML is not supported on Lynx elements.");
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
286
|
|
package/src/mount-redraw.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
//
|
|
12
12
|
// Deliberately NOT a queue/pubsub of multiple mounted apps (real Mithril's
|
|
13
13
|
// mount-redraw.js supports that because a browser page can `m.mount()`
|
|
14
|
-
// several independent roots) — mithril-lynx
|
|
14
|
+
// several independent roots) — mithril-lynx has exactly one `renderApp()`
|
|
15
15
|
// for the app's whole lifetime (plan §3.1), so "the current redraw
|
|
16
16
|
// function" is a single slot, not a list.
|
|
17
17
|
//
|
package/src/request.js
CHANGED
|
@@ -36,7 +36,7 @@ function checkUnsupported(options) {
|
|
|
36
36
|
for (const [name, matches] of UNSUPPORTED) {
|
|
37
37
|
if (matches(options)) {
|
|
38
38
|
throw new Error(
|
|
39
|
-
`[mithril-lynx
|
|
39
|
+
`[mithril-lynx/request] "${name}" is not supported — Lynx's fetch has no equivalent ` +
|
|
40
40
|
"(see FETCH_INVESTIGATION.md for exactly why). This throws instead of silently " +
|
|
41
41
|
"behaving differently from what you asked for.",
|
|
42
42
|
);
|
|
@@ -78,7 +78,7 @@ export function createRequestor(fetchImpl) {
|
|
|
78
78
|
|
|
79
79
|
if (typeof FormData !== "undefined" && options.body instanceof FormData) {
|
|
80
80
|
throw new Error(
|
|
81
|
-
"[mithril-lynx
|
|
81
|
+
"[mithril-lynx/request] FormData bodies are not supported — Lynx has no FormData " +
|
|
82
82
|
"at runtime (confirmed absent, see FETCH_INVESTIGATION.md). Use lynx.fetch directly " +
|
|
83
83
|
"if you have another way to send this data, or restructure it as plain JSON.",
|
|
84
84
|
);
|
package/src/route.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// `hasBeenResolved` gate) so porting route-using app code only requires
|
|
14
14
|
// swapping the import, not relearning the control flow. The one
|
|
15
15
|
// unavoidable signature change: `m.route(root, defaultRoute, routes)`
|
|
16
|
-
// loses `root` — there is no DOM node to point it at in
|
|
16
|
+
// loses `root` — there is no DOM node to point it at in this architecture
|
|
17
17
|
// (a single `renderApp()` for the app's whole lifetime, plan §3.1) — see
|
|
18
18
|
// the plan §5.2 for why that's a deliberate, documented deviation rather
|
|
19
19
|
// than a fake DOM node just to keep the arg count.
|
|
@@ -100,7 +100,7 @@ export function createRoute() {
|
|
|
100
100
|
/**
|
|
101
101
|
* @param {string} defaultRoute - Both the fallback for an unmatched path
|
|
102
102
|
* AND the screen the app starts on — there is no browser URL to read
|
|
103
|
-
* an initial path from, so this is the one path
|
|
103
|
+
* an initial path from, so this is the one path the app always starts at
|
|
104
104
|
* (the closest in-memory equivalent of React Router's
|
|
105
105
|
* `initialEntries={["/"]}`).
|
|
106
106
|
* @param {Record<string, unknown>} routes - Same shape as real
|
package/test/end-to-end.test.ts
CHANGED
|
@@ -11,8 +11,8 @@ import { createPatchApplier } from "../src/apply-patch.js";
|
|
|
11
11
|
// call anywhere in the view below) produces a second patch that updates
|
|
12
12
|
// the real tree again.
|
|
13
13
|
//
|
|
14
|
-
// This is the direct replacement for mithril-lynx
|
|
15
|
-
// `test/renderer-integration.test.ts` — same intent, rewritten for
|
|
14
|
+
// This is the direct replacement for the previous mithril-lynx's
|
|
15
|
+
// `test/renderer-integration.test.ts` — same intent, rewritten for this
|
|
16
16
|
// architecture (see mithril-lynx-v2-desde-cero.md §F1's acceptance
|
|
17
17
|
// criterion: this must pass from the first commit, never be fixed later).
|
|
18
18
|
|
|
@@ -14,7 +14,7 @@ describe("route.js + stable-host: a same-path re-resolve patches in place, never
|
|
|
14
14
|
it("swapping the live-bound view (module.hot.accept's job) produces only a SetText, no Create/Remove", () => {
|
|
15
15
|
lynxTestingEnv.switchToMainThread();
|
|
16
16
|
const capturedOps: unknown[][] = [];
|
|
17
|
-
lynx.getJSContext().addEventListener("
|
|
17
|
+
lynx.getJSContext().addEventListener("MithrilLynx:Patch", (event: any) => {
|
|
18
18
|
capturedOps.push(event.data);
|
|
19
19
|
});
|
|
20
20
|
|
package/test/route.test.ts
CHANGED
|
@@ -21,7 +21,7 @@ function setupRealTree() {
|
|
|
21
21
|
// on the main-thread side so ops can be applied and the resulting real
|
|
22
22
|
// tree inspected.
|
|
23
23
|
lynxTestingEnv.switchToMainThread();
|
|
24
|
-
lynx.getJSContext().addEventListener("
|
|
24
|
+
lynx.getJSContext().addEventListener("MithrilLynx:Patch", (event: any) => {
|
|
25
25
|
capturedOps.push(event.data);
|
|
26
26
|
lynxTestingEnv.switchToMainThread();
|
|
27
27
|
applier.applyPatch(event.data);
|
package/test/setup.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// test/setup.ts
|
|
2
2
|
//
|
|
3
3
|
// The minimal gap-fill on top of @lynx-js/testing-environment's own PAPI
|
|
4
|
-
// polyfill — same idea as mithril-lynx
|
|
5
|
-
// what
|
|
6
|
-
// see the
|
|
4
|
+
// polyfill — same idea as the previous mithril-lynx's testing.js, scoped
|
|
5
|
+
// down to only what apply-patch.js actually calls so far (no gestures/lists
|
|
6
|
+
// yet, see the plan's non-goals). `@lynx-js/testing-environment` already
|
|
7
7
|
// implements __CreateView/__CreateText/__CreateElement/__CreateRawText/
|
|
8
8
|
// __AppendElement/__InsertElementBefore/__RemoveElement/__SetAttribute/
|
|
9
9
|
// __SetClasses/__AddInlineStyle/__FlushElementTree/__GetElementUniqueID —
|
|
@@ -5,7 +5,7 @@ import { createVirtualBackend } from "../src/backends/virtual-backend.js";
|
|
|
5
5
|
import { createLynxDocument } from "../src/fake-dom.js";
|
|
6
6
|
import renderFactory from "mithril/render/render.js";
|
|
7
7
|
|
|
8
|
-
// Tests the
|
|
8
|
+
// Tests the plan's §3.6 hypothesis directly, WITHOUT a device: does
|
|
9
9
|
// re-rendering the SAME root/document with a structurally different tree
|
|
10
10
|
// (a sibling inserted next to an unrelated, focused-in-spirit node) reuse
|
|
11
11
|
// the unrelated node's id — i.e. does it survive as the SAME element,
|
|
@@ -20,7 +20,7 @@ import renderFactory from "mithril/render/render.js";
|
|
|
20
20
|
// `<input>` keeps keyboard focus and in-progress text is a device-only
|
|
21
21
|
// question (F4's real acceptance criterion).
|
|
22
22
|
|
|
23
|
-
describe("structural re-render reuses unrelated nodes (
|
|
23
|
+
describe("structural re-render reuses unrelated nodes (plan §3.6 hypothesis)", () => {
|
|
24
24
|
it("does not recreate a sibling `input`-like node when a new node is inserted next to it", () => {
|
|
25
25
|
const backend = createVirtualBackend();
|
|
26
26
|
const document = createLynxDocument(backend);
|
|
@@ -35,7 +35,7 @@ describe("structural re-render reuses unrelated nodes (v2 plan §3.6 hypothesis)
|
|
|
35
35
|
// Mithril's own (unkeyed-diff) middle-insertion trap to not apply —
|
|
36
36
|
// without keys, an unkeyed diff treats "insert in the middle" as
|
|
37
37
|
// "index 1 changed tag" and recreates everything from that index on,
|
|
38
|
-
// which is a real Mithril behavior, not a
|
|
38
|
+
// which is a real Mithril behavior, not a bug here. The un-keyed case
|
|
39
39
|
// is deliberately NOT what this test asserts.
|
|
40
40
|
let showExtra = false;
|
|
41
41
|
function view() {
|