From 9c1786691fe4c125589dd0838287c0b48e95a74d Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Thu, 11 Apr 2024 12:52:48 +0100 Subject: [PATCH 1/8] Update lock to latest canary --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index a4f45e37..09c25b43 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "is-in-ci": "^0.1.0", "lodash": "^4.17.21", "patch-console": "^2.0.0", - "react-reconciler": "^0.29.0", + "react-reconciler": "0.31.0-canary-4c12339ce-20240408", "scheduler": "^0.23.0", "signal-exit": "^3.0.7", "slice-ansi": "^7.1.0", @@ -74,10 +74,10 @@ "@types/benchmark": "^2.1.2", "@types/lodash": "^4.14.202", "@types/ms": "^0.7.31", - "@types/node": "^20.10.4", - "@types/react": "^18.2.43", - "@types/react-reconciler": "^0.28.2", - "@types/scheduler": "^0.16.8", + "@types/node": "*", + "@types/react": "^18.2.75", + "@types/react-reconciler": "^0.28.8", + "@types/scheduler": "^0.16.2", "@types/signal-exit": "^3.0.0", "@types/sinon": "^10.0.20", "@types/stack-utils": "^2.0.2", @@ -93,7 +93,7 @@ "node-pty": "^1.0.0", "p-queue": "^8.0.0", "prettier": "^3.1.1", - "react": "^18.0.0", + "react": "19.0.0-canary-4c12339ce-20240408", "react-devtools-core": "^5.0.0", "sinon": "^17.0.0", "strip-ansi": "^7.1.0", From c350bf93e1c93cd7a72920f6b4d6a985037246d8 Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Thu, 11 Apr 2024 12:53:31 +0100 Subject: [PATCH 2/8] Implement updatePriority tracking --- src/reconciler.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/reconciler.ts b/src/reconciler.ts index 1738f737..c1c2027b 100644 --- a/src/reconciler.ts +++ b/src/reconciler.ts @@ -1,6 +1,9 @@ import process from 'node:process'; import createReconciler from 'react-reconciler'; -import {DefaultEventPriority} from 'react-reconciler/constants.js'; +import { + DefaultEventPriority, + NoEventPriority +} from 'react-reconciler/constants.js'; import Yoga, {type Node as YogaNode} from 'yoga-wasm-web/auto'; import { createTextNode, @@ -92,6 +95,8 @@ type UpdatePayload = { style: Styles | undefined; }; +let currentUpdatePriority = NoEventPriority; + export default createReconciler< ElementNames, Props, @@ -231,7 +236,11 @@ export default createReconciler< scheduleTimeout: setTimeout, cancelTimeout: clearTimeout, noTimeout: -1, - getCurrentEventPriority: () => DefaultEventPriority, + setCurrentUpdatePriority: newPriority => { + currentUpdatePriority = newPriority; + }, + getCurrentUpdatePriority: () => currentUpdatePriority || DefaultEventPriority, + resolveUpdatePriority: () => DefaultEventPriority, beforeActiveInstanceBlur() {}, afterActiveInstanceBlur() {}, detachDeletedInstance() {}, From c8c10fcb39d80da3155b34ae6c04e8c551f068f3 Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Thu, 11 Apr 2024 14:31:07 +0100 Subject: [PATCH 3/8] resolveUpdatePriority should return current or default --- src/reconciler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reconciler.ts b/src/reconciler.ts index c1c2027b..20ab138a 100644 --- a/src/reconciler.ts +++ b/src/reconciler.ts @@ -239,8 +239,8 @@ export default createReconciler< setCurrentUpdatePriority: newPriority => { currentUpdatePriority = newPriority; }, - getCurrentUpdatePriority: () => currentUpdatePriority || DefaultEventPriority, - resolveUpdatePriority: () => DefaultEventPriority, + getCurrentUpdatePriority: () => currentUpdatePriority, + resolveUpdatePriority: () => currentUpdatePriority || DefaultEventPriority, beforeActiveInstanceBlur() {}, afterActiveInstanceBlur() {}, detachDeletedInstance() {}, From 188ce94d6295637d924e2b42953500d47a7ec59a Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Thu, 11 Apr 2024 17:19:45 +0100 Subject: [PATCH 4/8] Get hello world working --- src/ink.tsx | 28 ++++++++++++++++++++-------- src/reconciler.ts | 9 +++++++-- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/ink.tsx b/src/ink.tsx index 223c82fa..663ef628 100644 --- a/src/ink.tsx +++ b/src/ink.tsx @@ -77,17 +77,29 @@ export default class Ink { // so that it's rerendered every time, not just new static parts, like in non-debug mode this.fullStaticOutput = ''; + const rootTag = 1; + const hydrationCallbacks = null; + const isStrictMode = false; + const concurrentUpdatesByDefaultOverride = false; + const identifierPrefix = 'id'; + // TODO: Change error handling to noop. I've added this to more easily develop the reconciler + const onUncaughtError = console.error; + const onCaughtError = console.error; + const onRecoverableError = () => {}; + const transitionCallbacks = null; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this.container = reconciler.createContainer( this.rootNode, - // Legacy mode - 0, - null, - false, - null, - 'id', - () => {}, - null, + rootTag, + hydrationCallbacks, + isStrictMode, + concurrentUpdatesByDefaultOverride, + identifierPrefix, + onUncaughtError, + onCaughtError, + onRecoverableError, + transitionCallbacks, ); // Unmount when process exits diff --git a/src/reconciler.ts b/src/reconciler.ts index 20ab138a..7f02be0a 100644 --- a/src/reconciler.ts +++ b/src/reconciler.ts @@ -2,7 +2,7 @@ import process from 'node:process'; import createReconciler from 'react-reconciler'; import { DefaultEventPriority, - NoEventPriority + NoEventPriority, } from 'react-reconciler/constants.js'; import Yoga, {type Node as YogaNode} from 'yoga-wasm-web/auto'; import { @@ -271,7 +271,8 @@ export default createReconciler< return {props, style}; }, - commitUpdate(node, {props, style}) { + commitUpdate(node, payload, type, oldProps, newProps) { + const {props, style} = newProps; if (props) { for (const [key, value] of Object.entries(props)) { if (key === 'style') { @@ -304,4 +305,8 @@ export default createReconciler< removeChildNode(node, removeNode); cleanupYogaNode(removeNode.yogaNode); }, + maySuspendCommit() { + // TODO: May return false here if we are confident that we don't need to suspend + return true; + }, }); From 755a0bf948084b265d36e0acb83508e1a2489f70 Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Thu, 11 Apr 2024 18:13:42 +0100 Subject: [PATCH 5/8] Stub out suspensy things (suspense example now works) --- src/reconciler.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/reconciler.ts b/src/reconciler.ts index 7f02be0a..86085522 100644 --- a/src/reconciler.ts +++ b/src/reconciler.ts @@ -309,4 +309,13 @@ export default createReconciler< // TODO: May return false here if we are confident that we don't need to suspend return true; }, + startSuspendingCommit() {}, + waitForCommitToBeReady() { + return null; + }, + preloadInstance() { + // Return true to indicate it's already loaded + return true; + }, + suspendInstance() {}, }); From 31705ec01f5c01e41b8f45f7a066e11b2a052a38 Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Thu, 11 Apr 2024 18:39:59 +0100 Subject: [PATCH 6/8] console.error recoverable errors --- src/ink.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ink.tsx b/src/ink.tsx index 663ef628..3cf14875 100644 --- a/src/ink.tsx +++ b/src/ink.tsx @@ -85,7 +85,7 @@ export default class Ink { // TODO: Change error handling to noop. I've added this to more easily develop the reconciler const onUncaughtError = console.error; const onCaughtError = console.error; - const onRecoverableError = () => {}; + const onRecoverableError = console.error; const transitionCallbacks = null; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment From 72fc901dab76df25952b722cc980ce0c31d4646a Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Thu, 11 Apr 2024 18:46:05 +0100 Subject: [PATCH 7/8] Transitions --- src/reconciler.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/reconciler.ts b/src/reconciler.ts index 86085522..a6bb9e0a 100644 --- a/src/reconciler.ts +++ b/src/reconciler.ts @@ -318,4 +318,7 @@ export default createReconciler< return true; }, suspendInstance() {}, + shouldAttemptEagerTransition() { + return false; + }, }); From e1ac9e8c8b2ce1572e189d0dd257a892b81488f1 Mon Sep 17 00:00:00 2001 From: Tom Sherman Date: Mon, 15 Apr 2024 15:07:41 +0100 Subject: [PATCH 8/8] Remove batchedUpdates call as it's now a noop --- src/hooks/use-input.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/hooks/use-input.ts b/src/hooks/use-input.ts index a636d688..0e1f485f 100644 --- a/src/hooks/use-input.ts +++ b/src/hooks/use-input.ts @@ -182,10 +182,7 @@ const useInput = (inputHandler: Handler, options: Options = {}) => { // If app is not supposed to exit on Ctrl+C, then let input listener handle it if (!(input === 'c' && key.ctrl) || !internal_exitOnCtrlC) { - // @ts-expect-error TypeScript types for `batchedUpdates` require an argument, but React's codebase doesn't provide it and it works without it as exepected. - reconciler.batchedUpdates(() => { - inputHandler(input, key); - }); + inputHandler(input, key); } };