Skip to content

Backend-initiated block deletion (cmd:closeonexit / wsh deleteblock) leaves a dangling layout leaf; blockclose event has no frontend subscriber #3506

Description

@finedesignz

Version

Wave 0.14.5, Windows 11, pwsh 7 shells.

Symptom

A blank column stays in the tab where a block used to be. It persists across Wave restarts. Inspecting db_layout shows a leaf whose blockId is no longer present in tab.blockids / db_block -- the block row is gone but the layout leaf is not.

Repro

  1. Create a widget/block with cmd:closeonexit: true (or from inside a block run wsh deleteblock -b <id>).
  2. Let the shell command exit with code 0.
  3. After cmd:closeonexitdelay, the block row is deleted -- but the layout leaf for it remains as an empty column/leaf in the tab.

Log signature observed: a DeleteBlock: parentBlockCount: N line appears roughly 1.5s after ShellProcStatus: done, with no corresponding layout update.

Root cause (source refs, verified at tag v0.14.5)

  • pkg/blockcontroller/shellcontroller.go line 650, checkCloseOnExit, calls wshclient.DeleteBlockCommand at line 669.
  • pkg/wshrpc/wshserver/wshserver.go line 474, (ws *WshServer) DeleteBlockCommand, calls wcore.DeleteBlock(ctx, data.BlockId, true) at line 486.
  • pkg/wcore/block.go line 153, DeleteBlock, removes the block and (via deleteBlockObj) emits wps.Event_BlockClose at line 237.
  • frontend/app/store/global.ts line 55, initGlobalWaveEventSubs, subscribes to waveobj:update, config, waveai:modeconfig, userinput, blockfile, and waveai:ratelimit -- there is no subscription to a blockclose event anywhere in this function, so nothing dispatches LayoutTreeActionType.DeleteNode when the backend deletes a block on its own initiative.
  • frontend/layout/lib/layoutModel.ts line 411, cleanupOrphanedBlocks, only walks tab.blockids and checks whether each blockId exists in the layout tree (removing the ones that don't) -- it never walks the layout tree to find leaves whose blockId is absent from tab.blockids. So a leaf orphaned by a backend-initiated delete is never reconciled, even across a restart when cleanupOrphanedBlocks runs again.

Suggested fix

Either:

  1. Subscribe to the block-close event on the frontend (frontend/app/store/global.ts) and dispatch LayoutTreeActionType.DeleteNode against the affected tab's layout model, or
  2. Extend cleanupOrphanedBlocks (frontend/layout/lib/layoutModel.ts) to also prune layout leaves whose blockId is not present in tab.blockids, not just the reverse case it currently handles.

Workaround we use

An external script that deletes dangling leaves from db_layout while Wave is closed.


Report generated with help from Claude Code.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions