Conversation
There was a problem hiding this comment.
Now have webview manager handling webviews getting added and messages being sent to them
There was a problem hiding this comment.
Pull request overview
This PR improves Codescape’s VS Code extension state synchronization between the backend parse store and the webviews, aiming to fix initial load “dropped messages”, support multi-file coverage, and adjust the frontend rendering/layout logic.
Changes:
- Send a backend “FULL_STATE” snapshot to webviews after a READY handshake (panel + sidebar), and introduce a
broadcast()helper onJavaFileWatcher. - Update the panel/sidebar providers to receive
FileParseStoreand trigger full-state sync on webview readiness. - Update the webview renderer logic to support incremental updates (
PARTIAL_STATE), dependency-based layout, depth-sorted rendering, and hover interactions.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
| src/extension.ts | Adds full-state sync + READY handshake, wires store into webviews, and updates the embedded webview rendering/patching logic. |
| src/JavaFileWatcher.ts | Adds a broadcast() helper for sending messages to all registered webviews. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| }); | ||
| } | ||
|
|
||
| console.log("CREATE PANEL CALLED"); |
There was a problem hiding this comment.
console.log("CREATE PANEL CALLED") is at module top-level, so it will run on extension load even if the command is never invoked. This looks like leftover debug logging; consider removing it or moving it inside createPanel() behind a debug/trace flag to avoid noisy logs for all users.
| console.log("CREATE PANEL CALLED"); |
| if (msg.type === 'FULL_STATE' && msg.payload) { | ||
| console.log("CLASSES:", msg.payload.classes); | ||
| console.log('[FULL_STATE] received:', msg.payload); | ||
|
|
||
| state.classes = msg.payload.classes; | ||
|
|
||
| //build graph input | ||
| const nodes = buildNodesFromClasses(state.classes); | ||
|
|
||
| // run algorithm | ||
| state.layout = computeLayout(nodes); | ||
|
|
||
| assignColors(); | ||
| render(); |
There was a problem hiding this comment.
In the webview FULL_STATE handler, state.status is never updated after assigning state.classes. Since the initial status is "loading", render() will keep showing the loading UI and return early. Set state.status to "empty" or "ready" based on state.classes.length (and optionally handle an error case) before calling render().
| function patchState({ changed = [], related = [], removed = [] }) { | ||
| console.log("patchState called"); | ||
|
|
||
| const nodes = buildNodesFromClasses(state.classes); | ||
| state.layout = computeLayout(nodes); | ||
|
|
There was a problem hiding this comment.
patchState() computes state.layout from state.classes before applying removed/changed/related updates, so the layout can become stale (missing new classes, including removed ones). Apply the class list updates first, then rebuild nodes and recompute the layout from the updated state.classes.
| return classes.map(cls => { | ||
| const neighbors = []; | ||
|
|
||
| //extract dependencies from fields | ||
| if (cls.Fields) { | ||
| cls.Fields.forEach(field => { | ||
| const type = field.type; | ||
|
|
||
| //only include if it's another class in the project | ||
| if (classNames.has(type)) { | ||
| neighbors.push(type); |
There was a problem hiding this comment.
buildNodesFromClasses() compares field.type directly against known class names. Parser field types can include generics/arrays (e.g. List<Foo>/Foo[]), so these dependencies won’t be detected and neighbors will be incomplete. Normalize field.type (strip generic/array suffixes similar to relations.ts) before checking classNames.has(...).
| return classes.map(cls => { | |
| const neighbors = []; | |
| //extract dependencies from fields | |
| if (cls.Fields) { | |
| cls.Fields.forEach(field => { | |
| const type = field.type; | |
| //only include if it's another class in the project | |
| if (classNames.has(type)) { | |
| neighbors.push(type); | |
| // Normalize a raw field type by stripping array and generic suffixes. | |
| const normalizeType = (rawType: any): string | null => { | |
| if (typeof rawType !== "string") { | |
| return null; | |
| } | |
| let t = rawType.trim(); | |
| // Remove any number of trailing array brackets, e.g. Foo[][] -> Foo | |
| while (t.endsWith("[]")) { | |
| t = t.slice(0, -2).trim(); | |
| } | |
| // Remove a trailing generic part, e.g. List<Foo> -> List | |
| const genericStart = t.indexOf("<"); | |
| if (genericStart !== -1 && t.endsWith(">")) { | |
| t = t.substring(0, genericStart).trim(); | |
| } | |
| return t; | |
| }; | |
| return classes.map(cls => { | |
| const neighbors = []; | |
| //extract dependencies from fields | |
| if (cls.Fields) { | |
| cls.Fields.forEach(field => { | |
| const normalizedType = normalizeType(field.type); | |
| //only include if it's another class in the project | |
| if (normalizedType && classNames.has(normalizedType)) { | |
| neighbors.push(normalizedType); |
| const isoX = (position.col - position.row) * TILE_L / 2 + offsetX; | ||
| const isoY = (position.col + position.row) * TILE_L / 4 + offsetY; | ||
|
|
||
| placeIsoBuilding( | ||
| ctx, | ||
| position.col, | ||
| position.row, | ||
| floors, | ||
| state.colors[cls.Classname] || "#598BAF", | ||
| TILE_L, | ||
| offsetX, | ||
| offsetY | ||
| ); | ||
|
|
||
| const width = TILE_L; | ||
| const height = floors * TILE_L / 2; | ||
|
|
||
| buildingRegistry.push({ | ||
| className: cls.Classname, | ||
| x: isoX - width / 2, | ||
| y: isoY - height, | ||
| width: width, | ||
| height: height | ||
| }); |
There was a problem hiding this comment.
The hover hitbox Y-coordinate is computed from isoY, but placeIsoBuilding() renders the building with baseY = isoY + TILE_L / 2 (see renderer.js). This offset means buildingRegistry rectangles won’t line up with the drawn buildings, making hover detection inaccurate. Derive the registry box from the same baseY used for rendering.
| console.log("buildingRegistry:", buildingRegistry); | ||
|
|
There was a problem hiding this comment.
console.log("buildingRegistry:", buildingRegistry) runs once per class on every render, which can severely degrade canvas performance and spam logs as projects grow. Remove this or gate it behind an explicit debug flag.
| broadcast(message: any) { | ||
| if (this._webviews.length === 0) { | ||
| console.log("[broadcast] no webviews yet"); | ||
| return; | ||
| } | ||
|
|
||
| for (const v of this._webviews) { | ||
| v.postMessage(message).then(delivered => { | ||
| console.log("[broadcast] delivered:", delivered); | ||
| }); | ||
| } |
There was a problem hiding this comment.
broadcast() calls postMessage(...).then(...) without handling rejections. If a webview is disposed or postMessage fails, this can create unhandled promise rejections and leave dead webviews in _webviews. Add a .catch(...) (and optionally remove the failing webview from the list) to keep broadcasts reliable.
| broadcast(message: any) { | ||
| if (this._webviews.length === 0) { |
There was a problem hiding this comment.
broadcast(message: any) loses type safety and makes it easy to send malformed messages. Consider typing this parameter to a shared message contract (e.g. a WebviewMessage union) or at least unknown/Record<string, unknown> with runtime validation.
Fixed file discovery issues and multi-file coverage. I also found an algorithm rendering bug and fixed that.