Skip to content

Conversation

@franknoirot
Copy link
Contributor

Towards #8885, broken out from #9190. Refactors the KCL LSP extension to not store any EditorView or other state. In addition, splits out concerns of highlighting engine-side entities and executing code. Users should experience no degradations from this refactor, although it is possible that this fixes some niche case bugs.

@franknoirot franknoirot marked this pull request as ready for review December 10, 2025 02:29
@vercel
Copy link

vercel bot commented Dec 10, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
modeling-app Ready Ready Preview Comment Dec 10, 2025 7:10pm

@franknoirot franknoirot requested a review from a team as a code owner December 10, 2025 02:29
Comment on lines 40 to 77
const executeKclDeferred = deferExecution(
async ({ newCode }: { newCode: string }) => {
void systemDeps.kclManager.writeToFile(newCode)
await systemDeps.kclManager.executeCode(newCode)
},
changesDelay
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Race condition: writeToFile is not awaited before executeCode runs. The old code explicitly waited for the file write to complete via .then() before scheduling execution. Now execution can start while the file is still being written.

const executeKclDeferred = deferExecution(
  async ({ newCode }: { newCode: string }) => {
    await systemDeps.kclManager.writeToFile(newCode)
    await systemDeps.kclManager.executeCode(newCode)
  },
  changesDelay
)
Suggested change
const executeKclDeferred = deferExecution(
async ({ newCode }: { newCode: string }) => {
void systemDeps.kclManager.writeToFile(newCode)
await systemDeps.kclManager.executeCode(newCode)
},
changesDelay
)
const executeKclDeferred = deferExecution(
async ({ newCode }: { newCode: string }) => {
await systemDeps.kclManager.writeToFile(newCode)
await systemDeps.kclManager.executeCode(newCode)
},
changesDelay
)

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is a problem, writing to file at executing the code at the same time? Even if either fails we probably want to do the other?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's what I was thinking. I shouldn't change that when so many other critical things are being nudged though, so I reverted it anyway.


if (shouldExecute) {
const newCode = update.state.doc.toString()
executeKclDeferred({ newCode })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case newCode may not be the same as kclManager.code ?

}
async executeCode(): Promise<void> {
const ast = await this.safeParse(this.code)
async executeCode(newCode = this.code): Promise<void> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern of optional params that use a member property if not given can be error prone but here it's needed because of executeKclDeferred right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah I agree bad. It was my way to try to get new execution flow behavior (not relying on the duplicate kclManager.code state) to work without breaking all the call sites of kclManager.executeCode() scattered about, but it turns out this was the thing breaking that test! I've reverted it, thanks for highlighting it.

Copy link
Contributor

@andrewvarga andrewvarga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's so much nicer without the state in the plugin, less code as well, makes me wonder why we added that in the first place.
Great cleanup.

Comment on lines -57 to -66
// Gotcha: Code can be written into the CodeMirror editor but not propagated to kclManager.code
// because the update function has not run. We need to initialize the kclManager.code when lsp initializes
// because new code could have been written into the editor before the update callback is initialized.
// There appears to be limited ways to safely get the current doc content. This appears to be sync and safe.
const kclLspPlugin = this.client.plugins.find((plugin) => {
return plugin.client.name === 'kcl'
})
if (kclLspPlugin) {
systemDeps.kclManager.code = view.state.doc.toString()
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I revived this block of code as hackSetInitialCodeFromLsp, a "run once" extension that sets the code on kclManager initially. I believe we still need it until we get rid of React / tearing down the EditorView, although we have no automated tests that capture that.

Comment on lines -174 to -206
// If we're in sketchSolveMode, update Rust state with the latest AST
// This handles the case where the user directly edits in the CodeMirror editor
// these are short term hacks while in rapid development for sketch revamp
// should be clean up.
try {
const modelingState = kclManager.modelingState
const executeKclDeferred = deferExecution(
async ({ newCode }: { newCode: string }) => {
const modelingState = systemDeps.kclManager.modelingState
if (modelingState?.matches('sketchSolveMode')) {
await kclManager.executeCode()
const { sceneGraph, execOutcome } = await rustContext.hackSetProgram(
kclManager.ast,
await jsAppSettings()
)

// Convert SceneGraph to SceneGraphDelta and send to sketch solve machine
const sceneGraphDelta: SceneGraphDelta = {
new_graph: sceneGraph,
new_objects: [],
invalidates_ids: false,
exec_outcome: execOutcome,
}

const kclSource: SourceDelta = {
text: kclManager.code,
}

// Send event to sketch solve machine via modeling machine
kclManager.sendModelingEvent({
type: 'update sketch outcome',
data: {
kclSource,
sceneGraphDelta,
},
})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had completely overlooked this new hack code for sketch solve mode while I was initially writing this PR last night! I've revived it as hackExecutionForSketchSolve.

Comment on lines 40 to 77
const executeKclDeferred = deferExecution(
async ({ newCode }: { newCode: string }) => {
void systemDeps.kclManager.writeToFile(newCode)
await systemDeps.kclManager.executeCode(newCode)
},
changesDelay
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's what I was thinking. I shouldn't change that when so many other critical things are being nudged though, so I reverted it anyway.

sceneGraphDelta,
},
})
await hackExecutionForSketchSolve(newCode, systemDeps.kclManager)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing code assignment in sketch solve mode. When in sketchSolveMode, the kclManager.code is never updated with newCode, causing stale code to persist in the manager.

In the old implementation, kclManager.code was set before execution (line 149 in old code). In the refactored version, code is only set in the else branch (line 73), but not in the sketch solve path.

Fix:

await hackExecutionForSketchSolve(newCode, systemDeps.kclManager)
systemDeps.kclManager.code = newCode

Or alternatively, set it inside hackExecutionForSketchSolve before the execution.

Suggested change
await hackExecutionForSketchSolve(newCode, systemDeps.kclManager)
await hackExecutionForSketchSolve(newCode, systemDeps.kclManager)
systemDeps.kclManager.code = newCode

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Towards #8885, broken out from #9190. Refactors the KCL LSP extension to
not store any EditorView or other state. In addition, splits out
concerns of highlighting engine-side entities and executing code. Users
should experience no degradations from this refactor, although it is
possible that this fixes some niche case bugs.
@franknoirot franknoirot force-pushed the franknoirot/8885/refactor-kcl-execution-highlight-extensions branch from bfdf1a2 to 662c41f Compare December 10, 2025 18:46
@franknoirot franknoirot force-pushed the franknoirot/8885/refactor-kcl-execution-highlight-extensions branch from 662c41f to a84f53b Compare December 10, 2025 18:56
@franknoirot
Copy link
Contributor Author

This is tricky as shit!

  1. If I write kclManager.code = newCode within my deferredExecution function, one test fails
  2. If I don't do that and instead adjust kclManager.executeCode(newCode) to take a code parameter and not read from state, 18 tests fail

So annoying

@franknoirot franknoirot marked this pull request as draft December 10, 2025 20:00
@Irev-Dev
Copy link
Contributor

If I write kclManager.code = newCode within my deferredExecution function, one test fails

Is the failure showing a real problem? just wanna make sure it's not a case where the test just needs to be tweaked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants