@vsuryav/agent-sim 0.1.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.
- package/README.md +25 -0
- package/bin/agent-sim.js +25 -0
- package/package.json +72 -0
- package/src/app-paths.ts +29 -0
- package/src/app-sync.test.ts +75 -0
- package/src/app-sync.ts +110 -0
- package/src/cli.ts +129 -0
- package/src/collector/claude-code.test.ts +102 -0
- package/src/collector/claude-code.ts +133 -0
- package/src/collector/codex-cli.test.ts +116 -0
- package/src/collector/codex-cli.ts +149 -0
- package/src/collector/db.test.ts +59 -0
- package/src/collector/db.ts +125 -0
- package/src/collector/names.test.ts +21 -0
- package/src/collector/names.ts +28 -0
- package/src/collector/personality.test.ts +40 -0
- package/src/collector/personality.ts +46 -0
- package/src/collector/remote-sync.test.ts +31 -0
- package/src/collector/remote-sync.ts +171 -0
- package/src/collector/sync.test.ts +67 -0
- package/src/collector/sync.ts +148 -0
- package/src/collector/types.ts +1 -0
- package/src/engine/bootstrap/state.ts +3 -0
- package/src/engine/buddy/CompanionSprite.tsx +371 -0
- package/src/engine/buddy/companion.ts +133 -0
- package/src/engine/buddy/prompt.ts +36 -0
- package/src/engine/buddy/sprites.ts +514 -0
- package/src/engine/buddy/types.ts +148 -0
- package/src/engine/buddy/useBuddyNotification.tsx +98 -0
- package/src/engine/ink/Ansi.tsx +292 -0
- package/src/engine/ink/bidi.ts +139 -0
- package/src/engine/ink/clearTerminal.ts +74 -0
- package/src/engine/ink/colorize.ts +231 -0
- package/src/engine/ink/components/AlternateScreen.tsx +80 -0
- package/src/engine/ink/components/App.tsx +658 -0
- package/src/engine/ink/components/AppContext.ts +21 -0
- package/src/engine/ink/components/Box.tsx +214 -0
- package/src/engine/ink/components/Button.tsx +192 -0
- package/src/engine/ink/components/ClockContext.tsx +112 -0
- package/src/engine/ink/components/CursorDeclarationContext.ts +32 -0
- package/src/engine/ink/components/ErrorOverview.tsx +109 -0
- package/src/engine/ink/components/Link.tsx +42 -0
- package/src/engine/ink/components/Newline.tsx +39 -0
- package/src/engine/ink/components/NoSelect.tsx +68 -0
- package/src/engine/ink/components/RawAnsi.tsx +57 -0
- package/src/engine/ink/components/ScrollBox.tsx +237 -0
- package/src/engine/ink/components/Spacer.tsx +20 -0
- package/src/engine/ink/components/StdinContext.ts +49 -0
- package/src/engine/ink/components/TerminalFocusContext.tsx +52 -0
- package/src/engine/ink/components/TerminalSizeContext.tsx +7 -0
- package/src/engine/ink/components/Text.tsx +254 -0
- package/src/engine/ink/constants.ts +2 -0
- package/src/engine/ink/dom.ts +484 -0
- package/src/engine/ink/events/click-event.ts +38 -0
- package/src/engine/ink/events/dispatcher.ts +233 -0
- package/src/engine/ink/events/emitter.ts +39 -0
- package/src/engine/ink/events/event-handlers.ts +73 -0
- package/src/engine/ink/events/event.ts +11 -0
- package/src/engine/ink/events/focus-event.ts +21 -0
- package/src/engine/ink/events/input-event.ts +205 -0
- package/src/engine/ink/events/keyboard-event.ts +51 -0
- package/src/engine/ink/events/terminal-event.ts +107 -0
- package/src/engine/ink/events/terminal-focus-event.ts +19 -0
- package/src/engine/ink/focus.ts +181 -0
- package/src/engine/ink/frame.ts +124 -0
- package/src/engine/ink/get-max-width.ts +27 -0
- package/src/engine/ink/global.d.ts +18 -0
- package/src/engine/ink/hit-test.ts +130 -0
- package/src/engine/ink/hooks/use-animation-frame.ts +57 -0
- package/src/engine/ink/hooks/use-app.ts +8 -0
- package/src/engine/ink/hooks/use-declared-cursor.ts +73 -0
- package/src/engine/ink/hooks/use-input.ts +92 -0
- package/src/engine/ink/hooks/use-interval.ts +67 -0
- package/src/engine/ink/hooks/use-search-highlight.ts +53 -0
- package/src/engine/ink/hooks/use-selection.ts +104 -0
- package/src/engine/ink/hooks/use-stdin.ts +8 -0
- package/src/engine/ink/hooks/use-tab-status.ts +72 -0
- package/src/engine/ink/hooks/use-terminal-focus.ts +16 -0
- package/src/engine/ink/hooks/use-terminal-title.ts +31 -0
- package/src/engine/ink/hooks/use-terminal-viewport.ts +96 -0
- package/src/engine/ink/ink.tsx +1723 -0
- package/src/engine/ink/instances.ts +10 -0
- package/src/engine/ink/layout/engine.ts +6 -0
- package/src/engine/ink/layout/geometry.ts +97 -0
- package/src/engine/ink/layout/node.ts +152 -0
- package/src/engine/ink/layout/yoga.ts +308 -0
- package/src/engine/ink/line-width-cache.ts +24 -0
- package/src/engine/ink/log-update.ts +773 -0
- package/src/engine/ink/measure-element.ts +23 -0
- package/src/engine/ink/measure-text.ts +47 -0
- package/src/engine/ink/node-cache.ts +54 -0
- package/src/engine/ink/optimizer.ts +93 -0
- package/src/engine/ink/output.ts +797 -0
- package/src/engine/ink/parse-keypress.ts +801 -0
- package/src/engine/ink/reconciler.ts +512 -0
- package/src/engine/ink/render-border.ts +231 -0
- package/src/engine/ink/render-node-to-output.ts +1462 -0
- package/src/engine/ink/render-to-screen.ts +231 -0
- package/src/engine/ink/renderer.ts +178 -0
- package/src/engine/ink/root.ts +184 -0
- package/src/engine/ink/screen.ts +1486 -0
- package/src/engine/ink/searchHighlight.ts +93 -0
- package/src/engine/ink/selection.ts +917 -0
- package/src/engine/ink/squash-text-nodes.ts +92 -0
- package/src/engine/ink/stringWidth.ts +222 -0
- package/src/engine/ink/styles.ts +771 -0
- package/src/engine/ink/supports-hyperlinks.ts +57 -0
- package/src/engine/ink/tabstops.ts +46 -0
- package/src/engine/ink/terminal-focus-state.ts +47 -0
- package/src/engine/ink/terminal-querier.ts +212 -0
- package/src/engine/ink/terminal.ts +248 -0
- package/src/engine/ink/termio/ansi.ts +75 -0
- package/src/engine/ink/termio/csi.ts +319 -0
- package/src/engine/ink/termio/dec.ts +60 -0
- package/src/engine/ink/termio/esc.ts +67 -0
- package/src/engine/ink/termio/osc.ts +493 -0
- package/src/engine/ink/termio/parser.ts +394 -0
- package/src/engine/ink/termio/sgr.ts +308 -0
- package/src/engine/ink/termio/tokenize.ts +319 -0
- package/src/engine/ink/termio/types.ts +236 -0
- package/src/engine/ink/useTerminalNotification.ts +126 -0
- package/src/engine/ink/warn.ts +9 -0
- package/src/engine/ink/widest-line.ts +19 -0
- package/src/engine/ink/wrap-text.ts +74 -0
- package/src/engine/ink/wrapAnsi.ts +20 -0
- package/src/engine/native-ts/yoga-layout/enums.ts +134 -0
- package/src/engine/native-ts/yoga-layout/index.ts +2578 -0
- package/src/engine/stubs/bootstrap-state.ts +4 -0
- package/src/engine/stubs/debug.ts +6 -0
- package/src/engine/stubs/log.ts +4 -0
- package/src/engine/utils/debug.ts +5 -0
- package/src/engine/utils/earlyInput.ts +4 -0
- package/src/engine/utils/env.ts +15 -0
- package/src/engine/utils/envUtils.ts +4 -0
- package/src/engine/utils/execFileNoThrow.ts +24 -0
- package/src/engine/utils/fullscreen.ts +4 -0
- package/src/engine/utils/intl.ts +9 -0
- package/src/engine/utils/log.ts +3 -0
- package/src/engine/utils/semver.ts +13 -0
- package/src/engine/utils/sliceAnsi.ts +10 -0
- package/src/engine/utils/theme.ts +17 -0
- package/src/game/App.tsx +141 -0
- package/src/game/agents/behavior.ts +249 -0
- package/src/game/agents/speech.ts +57 -0
- package/src/game/canvas.ts +98 -0
- package/src/game/launch.ts +36 -0
- package/src/game/ship/ShipView.tsx +145 -0
- package/src/game/ship/ship-map.ts +172 -0
- package/src/game/ui/AgentBio.tsx +72 -0
- package/src/game/ui/HUD.tsx +63 -0
- package/src/game/ui/StatusBar.tsx +49 -0
- package/src/game/useKeyboard.ts +62 -0
- package/src/main.tsx +22 -0
- package/src/run-interactive.ts +74 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
/* eslint-disable custom-rules/no-top-level-side-effects */
|
|
2
|
+
|
|
3
|
+
import { appendFileSync } from 'fs'
|
|
4
|
+
import createReconciler from 'react-reconciler'
|
|
5
|
+
import { getYogaCounters } from '../native-ts/yoga-layout/index.js'
|
|
6
|
+
import { isEnvTruthy } from '../utils/envUtils.js'
|
|
7
|
+
import {
|
|
8
|
+
appendChildNode,
|
|
9
|
+
clearYogaNodeReferences,
|
|
10
|
+
createNode,
|
|
11
|
+
createTextNode,
|
|
12
|
+
type DOMElement,
|
|
13
|
+
type DOMNodeAttribute,
|
|
14
|
+
type ElementNames,
|
|
15
|
+
insertBeforeNode,
|
|
16
|
+
markDirty,
|
|
17
|
+
removeChildNode,
|
|
18
|
+
setAttribute,
|
|
19
|
+
setStyle,
|
|
20
|
+
setTextNodeValue,
|
|
21
|
+
setTextStyles,
|
|
22
|
+
type TextNode,
|
|
23
|
+
} from './dom.js'
|
|
24
|
+
import { Dispatcher } from './events/dispatcher.js'
|
|
25
|
+
import { EVENT_HANDLER_PROPS } from './events/event-handlers.js'
|
|
26
|
+
import { getFocusManager, getRootNode } from './focus.js'
|
|
27
|
+
import { LayoutDisplay } from './layout/node.js'
|
|
28
|
+
import applyStyles, { type Styles, type TextStyles } from './styles.js'
|
|
29
|
+
|
|
30
|
+
// We need to conditionally perform devtools connection to avoid
|
|
31
|
+
// accidentally breaking other third-party code.
|
|
32
|
+
// See https://github.com/vadimdemedes/ink/issues/384
|
|
33
|
+
if (process.env.NODE_ENV === 'development') {
|
|
34
|
+
try {
|
|
35
|
+
// eslint-disable-next-line custom-rules/no-top-level-dynamic-import -- dev-only; NODE_ENV check is DCE'd in production
|
|
36
|
+
void import('./devtools.js')
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
} catch (error: any) {
|
|
39
|
+
if (error.code === 'ERR_MODULE_NOT_FOUND') {
|
|
40
|
+
// biome-ignore lint/suspicious/noConsole: intentional warning
|
|
41
|
+
console.warn(
|
|
42
|
+
`
|
|
43
|
+
The environment variable DEV is set to true, so Ink tried to import \`react-devtools-core\`,
|
|
44
|
+
but this failed as it was not installed. Debugging with React Devtools requires it.
|
|
45
|
+
|
|
46
|
+
To install use this command:
|
|
47
|
+
|
|
48
|
+
$ npm install --save-dev react-devtools-core
|
|
49
|
+
`.trim() + '\n',
|
|
50
|
+
)
|
|
51
|
+
} else {
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
53
|
+
throw error
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// --
|
|
59
|
+
|
|
60
|
+
type AnyObject = Record<string, unknown>
|
|
61
|
+
|
|
62
|
+
const diff = (before: AnyObject, after: AnyObject): AnyObject | undefined => {
|
|
63
|
+
if (before === after) {
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!before) {
|
|
68
|
+
return after
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const changed: AnyObject = {}
|
|
72
|
+
let isChanged = false
|
|
73
|
+
|
|
74
|
+
for (const key of Object.keys(before)) {
|
|
75
|
+
const isDeleted = after ? !Object.hasOwn(after, key) : true
|
|
76
|
+
|
|
77
|
+
if (isDeleted) {
|
|
78
|
+
changed[key] = undefined
|
|
79
|
+
isChanged = true
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (after) {
|
|
84
|
+
for (const key of Object.keys(after)) {
|
|
85
|
+
if (after[key] !== before[key]) {
|
|
86
|
+
changed[key] = after[key]
|
|
87
|
+
isChanged = true
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return isChanged ? changed : undefined
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const cleanupYogaNode = (node: DOMElement | TextNode): void => {
|
|
96
|
+
const yogaNode = node.yogaNode
|
|
97
|
+
if (yogaNode) {
|
|
98
|
+
yogaNode.unsetMeasureFunc()
|
|
99
|
+
// Clear all references BEFORE freeing to prevent other code from
|
|
100
|
+
// accessing freed WASM memory during concurrent operations
|
|
101
|
+
clearYogaNodeReferences(node)
|
|
102
|
+
yogaNode.freeRecursive()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// --
|
|
107
|
+
|
|
108
|
+
type Props = Record<string, unknown>
|
|
109
|
+
|
|
110
|
+
type HostContext = {
|
|
111
|
+
isInsideText: boolean
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function setEventHandler(node: DOMElement, key: string, value: unknown): void {
|
|
115
|
+
if (!node._eventHandlers) {
|
|
116
|
+
node._eventHandlers = {}
|
|
117
|
+
}
|
|
118
|
+
node._eventHandlers[key] = value
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function applyProp(node: DOMElement, key: string, value: unknown): void {
|
|
122
|
+
if (key === 'children') return
|
|
123
|
+
|
|
124
|
+
if (key === 'style') {
|
|
125
|
+
setStyle(node, value as Styles)
|
|
126
|
+
if (node.yogaNode) {
|
|
127
|
+
applyStyles(node.yogaNode, value as Styles)
|
|
128
|
+
}
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (key === 'textStyles') {
|
|
133
|
+
node.textStyles = value as TextStyles
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (EVENT_HANDLER_PROPS.has(key)) {
|
|
138
|
+
setEventHandler(node, key, value)
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
setAttribute(node, key, value as DOMNodeAttribute)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// --
|
|
146
|
+
|
|
147
|
+
// react-reconciler's Fiber shape — only the fields we walk. The 5th arg to
|
|
148
|
+
// createInstance is the Fiber (`workInProgress` in react-reconciler.dev.js).
|
|
149
|
+
// _debugOwner is the component that rendered this element (dev builds only);
|
|
150
|
+
// return is the parent fiber (always present). We prefer _debugOwner since it
|
|
151
|
+
// skips past Box/Text wrappers to the actual named component.
|
|
152
|
+
type FiberLike = {
|
|
153
|
+
elementType?: { displayName?: string; name?: string } | string | null
|
|
154
|
+
_debugOwner?: FiberLike | null
|
|
155
|
+
return?: FiberLike | null
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function getOwnerChain(fiber: unknown): string[] {
|
|
159
|
+
const chain: string[] = []
|
|
160
|
+
const seen = new Set<unknown>()
|
|
161
|
+
let cur = fiber as FiberLike | null | undefined
|
|
162
|
+
for (let i = 0; cur && i < 50; i++) {
|
|
163
|
+
if (seen.has(cur)) break
|
|
164
|
+
seen.add(cur)
|
|
165
|
+
const t = cur.elementType
|
|
166
|
+
const name =
|
|
167
|
+
typeof t === 'function'
|
|
168
|
+
? (t as { displayName?: string; name?: string }).displayName ||
|
|
169
|
+
(t as { displayName?: string; name?: string }).name
|
|
170
|
+
: typeof t === 'string'
|
|
171
|
+
? undefined // host element (ink-box etc) — skip
|
|
172
|
+
: t?.displayName || t?.name
|
|
173
|
+
if (name && name !== chain[chain.length - 1]) chain.push(name)
|
|
174
|
+
cur = cur._debugOwner ?? cur.return
|
|
175
|
+
}
|
|
176
|
+
return chain
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let debugRepaints: boolean | undefined
|
|
180
|
+
export function isDebugRepaintsEnabled(): boolean {
|
|
181
|
+
if (debugRepaints === undefined) {
|
|
182
|
+
debugRepaints = isEnvTruthy(process.env.CLAUDE_CODE_DEBUG_REPAINTS)
|
|
183
|
+
}
|
|
184
|
+
return debugRepaints
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export const dispatcher = new Dispatcher()
|
|
188
|
+
|
|
189
|
+
// --- COMMIT INSTRUMENTATION (temp debugging) ---
|
|
190
|
+
// eslint-disable-next-line custom-rules/no-process-env-top-level -- debug instrumentation, read-once is fine
|
|
191
|
+
const COMMIT_LOG = process.env.CLAUDE_CODE_COMMIT_LOG
|
|
192
|
+
let _commits = 0
|
|
193
|
+
let _lastLog = 0
|
|
194
|
+
let _lastCommitAt = 0
|
|
195
|
+
let _maxGapMs = 0
|
|
196
|
+
let _createCount = 0
|
|
197
|
+
let _prepareAt = 0
|
|
198
|
+
// --- END ---
|
|
199
|
+
|
|
200
|
+
// --- SCROLL PROFILING (bench/scroll-e2e.sh reads via getLastYogaMs) ---
|
|
201
|
+
// Set by onComputeLayout wrapper in ink.tsx; read by onRender for phases.
|
|
202
|
+
let _lastYogaMs = 0
|
|
203
|
+
let _lastCommitMs = 0
|
|
204
|
+
let _commitStart = 0
|
|
205
|
+
export function recordYogaMs(ms: number): void {
|
|
206
|
+
_lastYogaMs = ms
|
|
207
|
+
}
|
|
208
|
+
export function getLastYogaMs(): number {
|
|
209
|
+
return _lastYogaMs
|
|
210
|
+
}
|
|
211
|
+
export function markCommitStart(): void {
|
|
212
|
+
_commitStart = performance.now()
|
|
213
|
+
}
|
|
214
|
+
export function getLastCommitMs(): number {
|
|
215
|
+
return _lastCommitMs
|
|
216
|
+
}
|
|
217
|
+
export function resetProfileCounters(): void {
|
|
218
|
+
_lastYogaMs = 0
|
|
219
|
+
_lastCommitMs = 0
|
|
220
|
+
_commitStart = 0
|
|
221
|
+
}
|
|
222
|
+
// --- END ---
|
|
223
|
+
|
|
224
|
+
const reconciler = createReconciler<
|
|
225
|
+
ElementNames,
|
|
226
|
+
Props,
|
|
227
|
+
DOMElement,
|
|
228
|
+
DOMElement,
|
|
229
|
+
TextNode,
|
|
230
|
+
DOMElement,
|
|
231
|
+
unknown,
|
|
232
|
+
unknown,
|
|
233
|
+
DOMElement,
|
|
234
|
+
HostContext,
|
|
235
|
+
null, // UpdatePayload - not used in React 19
|
|
236
|
+
NodeJS.Timeout,
|
|
237
|
+
-1,
|
|
238
|
+
null
|
|
239
|
+
>({
|
|
240
|
+
getRootHostContext: () => ({ isInsideText: false }),
|
|
241
|
+
prepareForCommit: () => {
|
|
242
|
+
if (COMMIT_LOG) _prepareAt = performance.now()
|
|
243
|
+
return null
|
|
244
|
+
},
|
|
245
|
+
preparePortalMount: () => null,
|
|
246
|
+
clearContainer: () => false,
|
|
247
|
+
resetAfterCommit(rootNode) {
|
|
248
|
+
_lastCommitMs = _commitStart > 0 ? performance.now() - _commitStart : 0
|
|
249
|
+
_commitStart = 0
|
|
250
|
+
if (COMMIT_LOG) {
|
|
251
|
+
const now = performance.now()
|
|
252
|
+
_commits++
|
|
253
|
+
const gap = _lastCommitAt > 0 ? now - _lastCommitAt : 0
|
|
254
|
+
if (gap > _maxGapMs) _maxGapMs = gap
|
|
255
|
+
_lastCommitAt = now
|
|
256
|
+
const reconcileMs = _prepareAt > 0 ? now - _prepareAt : 0
|
|
257
|
+
if (gap > 30 || reconcileMs > 20 || _createCount > 50) {
|
|
258
|
+
// eslint-disable-next-line custom-rules/no-sync-fs -- debug instrumentation
|
|
259
|
+
appendFileSync(
|
|
260
|
+
COMMIT_LOG,
|
|
261
|
+
`${now.toFixed(1)} gap=${gap.toFixed(1)}ms reconcile=${reconcileMs.toFixed(1)}ms creates=${_createCount}\n`,
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
_createCount = 0
|
|
265
|
+
if (now - _lastLog > 1000) {
|
|
266
|
+
// eslint-disable-next-line custom-rules/no-sync-fs -- debug instrumentation
|
|
267
|
+
appendFileSync(
|
|
268
|
+
COMMIT_LOG,
|
|
269
|
+
`${now.toFixed(1)} commits=${_commits}/s maxGap=${_maxGapMs.toFixed(1)}ms\n`,
|
|
270
|
+
)
|
|
271
|
+
_commits = 0
|
|
272
|
+
_maxGapMs = 0
|
|
273
|
+
_lastLog = now
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const _t0 = COMMIT_LOG ? performance.now() : 0
|
|
277
|
+
if (typeof rootNode.onComputeLayout === 'function') {
|
|
278
|
+
rootNode.onComputeLayout()
|
|
279
|
+
}
|
|
280
|
+
if (COMMIT_LOG) {
|
|
281
|
+
const layoutMs = performance.now() - _t0
|
|
282
|
+
if (layoutMs > 20) {
|
|
283
|
+
const c = getYogaCounters()
|
|
284
|
+
// eslint-disable-next-line custom-rules/no-sync-fs -- debug instrumentation
|
|
285
|
+
appendFileSync(
|
|
286
|
+
COMMIT_LOG,
|
|
287
|
+
`${_t0.toFixed(1)} SLOW_YOGA ${layoutMs.toFixed(1)}ms visited=${c.visited} measured=${c.measured} hits=${c.cacheHits} live=${c.live}\n`,
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (process.env.NODE_ENV === 'test') {
|
|
293
|
+
if (rootNode.childNodes.length === 0 && rootNode.hasRenderedContent) {
|
|
294
|
+
return
|
|
295
|
+
}
|
|
296
|
+
if (rootNode.childNodes.length > 0) {
|
|
297
|
+
rootNode.hasRenderedContent = true
|
|
298
|
+
}
|
|
299
|
+
rootNode.onImmediateRender?.()
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const _tr = COMMIT_LOG ? performance.now() : 0
|
|
304
|
+
rootNode.onRender?.()
|
|
305
|
+
if (COMMIT_LOG) {
|
|
306
|
+
const renderMs = performance.now() - _tr
|
|
307
|
+
if (renderMs > 10) {
|
|
308
|
+
// eslint-disable-next-line custom-rules/no-sync-fs -- debug instrumentation
|
|
309
|
+
appendFileSync(
|
|
310
|
+
COMMIT_LOG,
|
|
311
|
+
`${_tr.toFixed(1)} SLOW_PAINT ${renderMs.toFixed(1)}ms\n`,
|
|
312
|
+
)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
getChildHostContext(
|
|
317
|
+
parentHostContext: HostContext,
|
|
318
|
+
type: ElementNames,
|
|
319
|
+
): HostContext {
|
|
320
|
+
const previousIsInsideText = parentHostContext.isInsideText
|
|
321
|
+
const isInsideText =
|
|
322
|
+
type === 'ink-text' || type === 'ink-virtual-text' || type === 'ink-link'
|
|
323
|
+
|
|
324
|
+
if (previousIsInsideText === isInsideText) {
|
|
325
|
+
return parentHostContext
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return { isInsideText }
|
|
329
|
+
},
|
|
330
|
+
shouldSetTextContent: () => false,
|
|
331
|
+
createInstance(
|
|
332
|
+
originalType: ElementNames,
|
|
333
|
+
newProps: Props,
|
|
334
|
+
_root: DOMElement,
|
|
335
|
+
hostContext: HostContext,
|
|
336
|
+
internalHandle?: unknown,
|
|
337
|
+
): DOMElement {
|
|
338
|
+
if (hostContext.isInsideText && originalType === 'ink-box') {
|
|
339
|
+
throw new Error(`<Box> can't be nested inside <Text> component`)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const type =
|
|
343
|
+
originalType === 'ink-text' && hostContext.isInsideText
|
|
344
|
+
? 'ink-virtual-text'
|
|
345
|
+
: originalType
|
|
346
|
+
|
|
347
|
+
const node = createNode(type)
|
|
348
|
+
if (COMMIT_LOG) _createCount++
|
|
349
|
+
|
|
350
|
+
for (const [key, value] of Object.entries(newProps)) {
|
|
351
|
+
applyProp(node, key, value)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (isDebugRepaintsEnabled()) {
|
|
355
|
+
node.debugOwnerChain = getOwnerChain(internalHandle)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return node
|
|
359
|
+
},
|
|
360
|
+
createTextInstance(
|
|
361
|
+
text: string,
|
|
362
|
+
_root: DOMElement,
|
|
363
|
+
hostContext: HostContext,
|
|
364
|
+
): TextNode {
|
|
365
|
+
if (!hostContext.isInsideText) {
|
|
366
|
+
throw new Error(
|
|
367
|
+
`Text string "${text}" must be rendered inside <Text> component`,
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return createTextNode(text)
|
|
372
|
+
},
|
|
373
|
+
resetTextContent() {},
|
|
374
|
+
hideTextInstance(node) {
|
|
375
|
+
setTextNodeValue(node, '')
|
|
376
|
+
},
|
|
377
|
+
unhideTextInstance(node, text) {
|
|
378
|
+
setTextNodeValue(node, text)
|
|
379
|
+
},
|
|
380
|
+
getPublicInstance: (instance): DOMElement => instance as DOMElement,
|
|
381
|
+
hideInstance(node) {
|
|
382
|
+
node.isHidden = true
|
|
383
|
+
node.yogaNode?.setDisplay(LayoutDisplay.None)
|
|
384
|
+
markDirty(node)
|
|
385
|
+
},
|
|
386
|
+
unhideInstance(node) {
|
|
387
|
+
node.isHidden = false
|
|
388
|
+
node.yogaNode?.setDisplay(LayoutDisplay.Flex)
|
|
389
|
+
markDirty(node)
|
|
390
|
+
},
|
|
391
|
+
appendInitialChild: appendChildNode,
|
|
392
|
+
appendChild: appendChildNode,
|
|
393
|
+
insertBefore: insertBeforeNode,
|
|
394
|
+
finalizeInitialChildren(
|
|
395
|
+
_node: DOMElement,
|
|
396
|
+
_type: ElementNames,
|
|
397
|
+
props: Props,
|
|
398
|
+
): boolean {
|
|
399
|
+
return props['autoFocus'] === true
|
|
400
|
+
},
|
|
401
|
+
commitMount(node: DOMElement): void {
|
|
402
|
+
getFocusManager(node).handleAutoFocus(node)
|
|
403
|
+
},
|
|
404
|
+
isPrimaryRenderer: true,
|
|
405
|
+
supportsMutation: true,
|
|
406
|
+
supportsPersistence: false,
|
|
407
|
+
supportsHydration: false,
|
|
408
|
+
scheduleTimeout: setTimeout,
|
|
409
|
+
cancelTimeout: clearTimeout,
|
|
410
|
+
noTimeout: -1,
|
|
411
|
+
getCurrentUpdatePriority: () => dispatcher.currentUpdatePriority,
|
|
412
|
+
beforeActiveInstanceBlur() {},
|
|
413
|
+
afterActiveInstanceBlur() {},
|
|
414
|
+
detachDeletedInstance() {},
|
|
415
|
+
getInstanceFromNode: () => null,
|
|
416
|
+
prepareScopeUpdate() {},
|
|
417
|
+
getInstanceFromScope: () => null,
|
|
418
|
+
appendChildToContainer: appendChildNode,
|
|
419
|
+
insertInContainerBefore: insertBeforeNode,
|
|
420
|
+
removeChildFromContainer(node: DOMElement, removeNode: DOMElement): void {
|
|
421
|
+
removeChildNode(node, removeNode)
|
|
422
|
+
cleanupYogaNode(removeNode)
|
|
423
|
+
getFocusManager(node).handleNodeRemoved(removeNode, node)
|
|
424
|
+
},
|
|
425
|
+
// React 19 commitUpdate receives old and new props directly instead of an updatePayload
|
|
426
|
+
commitUpdate(
|
|
427
|
+
node: DOMElement,
|
|
428
|
+
_type: ElementNames,
|
|
429
|
+
oldProps: Props,
|
|
430
|
+
newProps: Props,
|
|
431
|
+
): void {
|
|
432
|
+
const props = diff(oldProps, newProps)
|
|
433
|
+
const style = diff(oldProps['style'] as Styles, newProps['style'] as Styles)
|
|
434
|
+
|
|
435
|
+
if (props) {
|
|
436
|
+
for (const [key, value] of Object.entries(props)) {
|
|
437
|
+
if (key === 'style') {
|
|
438
|
+
setStyle(node, value as Styles)
|
|
439
|
+
continue
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (key === 'textStyles') {
|
|
443
|
+
setTextStyles(node, value as TextStyles)
|
|
444
|
+
continue
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (EVENT_HANDLER_PROPS.has(key)) {
|
|
448
|
+
setEventHandler(node, key, value)
|
|
449
|
+
continue
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
setAttribute(node, key, value as DOMNodeAttribute)
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (style && node.yogaNode) {
|
|
457
|
+
applyStyles(node.yogaNode, style, newProps['style'] as Styles)
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
commitTextUpdate(node: TextNode, _oldText: string, newText: string): void {
|
|
461
|
+
setTextNodeValue(node, newText)
|
|
462
|
+
},
|
|
463
|
+
removeChild(node, removeNode) {
|
|
464
|
+
removeChildNode(node, removeNode)
|
|
465
|
+
cleanupYogaNode(removeNode)
|
|
466
|
+
if (removeNode.nodeName !== '#text') {
|
|
467
|
+
const root = getRootNode(node)
|
|
468
|
+
root.focusManager!.handleNodeRemoved(removeNode, root)
|
|
469
|
+
}
|
|
470
|
+
},
|
|
471
|
+
// React 19 required methods
|
|
472
|
+
maySuspendCommit(): boolean {
|
|
473
|
+
return false
|
|
474
|
+
},
|
|
475
|
+
preloadInstance(): boolean {
|
|
476
|
+
return true
|
|
477
|
+
},
|
|
478
|
+
startSuspendingCommit(): void {},
|
|
479
|
+
suspendInstance(): void {},
|
|
480
|
+
waitForCommitToBeReady(): null {
|
|
481
|
+
return null
|
|
482
|
+
},
|
|
483
|
+
NotPendingTransition: null,
|
|
484
|
+
HostTransitionContext: {
|
|
485
|
+
$$typeof: Symbol.for('react.context'),
|
|
486
|
+
_currentValue: null,
|
|
487
|
+
} as never,
|
|
488
|
+
setCurrentUpdatePriority(newPriority: number): void {
|
|
489
|
+
dispatcher.currentUpdatePriority = newPriority
|
|
490
|
+
},
|
|
491
|
+
resolveUpdatePriority(): number {
|
|
492
|
+
return dispatcher.resolveEventPriority()
|
|
493
|
+
},
|
|
494
|
+
resetFormInstance(): void {},
|
|
495
|
+
requestPostPaintCallback(): void {},
|
|
496
|
+
shouldAttemptEagerTransition(): boolean {
|
|
497
|
+
return false
|
|
498
|
+
},
|
|
499
|
+
trackSchedulerEvent(): void {},
|
|
500
|
+
resolveEventType(): string | null {
|
|
501
|
+
return dispatcher.currentEvent?.type ?? null
|
|
502
|
+
},
|
|
503
|
+
resolveEventTimeStamp(): number {
|
|
504
|
+
return dispatcher.currentEvent?.timeStamp ?? -1.1
|
|
505
|
+
},
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
// Wire the reconciler's discreteUpdates into the dispatcher.
|
|
509
|
+
// This breaks the import cycle: dispatcher.ts doesn't import reconciler.ts.
|
|
510
|
+
dispatcher.discreteUpdates = reconciler.discreteUpdates.bind(reconciler)
|
|
511
|
+
|
|
512
|
+
export default reconciler
|