Skip to content

Fix jupyter console frame and add to frame type selector#8750

Merged
haraldschilly merged 9 commits intomasterfrom
fix-jupyter-console-20260219
Feb 20, 2026
Merged

Fix jupyter console frame and add to frame type selector#8750
haraldschilly merged 9 commits intomasterfrom
fix-jupyter-console-20260219

Conversation

@haraldschilly
Copy link
Contributor

@haraldschilly haraldschilly commented Feb 19, 2026

Warning

Project restart required. This PR includes backend changes to packages/project/conat/terminal/manager.ts (terminal session lifecycle). After deploying, projects must be restarted for the fix to take effect.

Summary

  • Bug 1 — Console never renders: The jupyter console (View > Console in a notebook) opened a panel showing "jupyter console --existing PATH" but the actual terminal never appeared. Root cause: frame tree args stored as Immutable.js Lists failed MsgPack serialization over conat, so the backend received garbled args and could not spawn the process. Fixed by adding normalizeArgs() to convert Immutable Lists to plain string[].

  • Bug 2 — Console not in frame type selector: The jupyter console was only accessible from the View menu, not the frame type selector ("+" button). It was also mis-classified as "Terminal" in the title bar. Fixed by introducing a dedicated "shell" frame type in the jupyter EDITOR_SPEC, with overrides for shell(), new_frame(), and set_frame_type() in JupyterEditorActions.

  • Bug 3 — Terminal lifecycle for shell frames (codex review follow-up):

    • clear() action threw "not implemented" for shell frames — fixed by handling type === "shell" alongside "terminal".
    • Switching frame type (terminal ↔ shell) didn't update the live terminal process — fixed by using close_terminal() + set_frame_tree(). The TerminalFrame component now watches frameType, command, and args for changes and reinitializes automatically.
    • watchJupyterStore now watches both backend_state and connection_file and updates ALL shell frames, not just the most recent. connection_file alone is unreliable since it persists after kernel stops.
    • Shell → terminal switching now properly clears command/args so it reverts to bash.
    • Hidden frames clear stale terminal refs so visibility effect can reinit later.
  • Bug 4 — Backend terminal session zombies:

    • Backend TerminalManager.kill() only called session.close() without removing the session from its maps, leaving zombie entries that caused blank terminals on reconnect. Fixed by using closeTerminal() which properly removes session, service, and options entries.
    • CreateTerminalOptions were stored in a closure variable that was never updated after initial service creation. Moved to a persistent options map so reconnecting with a new connection_file actually starts a fresh session.
    • createTerminalService() now reapplies options via createTerminal() even when the service already exists.
  • Bug 5 — UX for kernel-not-running state:

    • Shell frames now show a "Kernel not running. Run a notebook cell to start the kernel and connect console." message instead of a blank terminal or connection error.
    • New hasLiveKernelConnection() gate requires project running + backend_state === "running" + connection_file present. Used in setShellFrameCommand() and get_shell_spec().
    • When kernel starts, watchJupyterStore detects the state change and automatically connects existing shell frames.
  • Bug 6 — Stale session on console close/reopen & project state sync:

    • watchJupyterStore now also watches the projects store for project run-state changes, resyncing shell frames when the project stops/starts.
    • Initial syncShellFrames() call on setup ensures page reload immediately reconciles shell frames with current kernel/project state.
  • Bug 7 — View > Console fell back to plain terminal when kernel not running:

    • shell() now always creates/reuses a "shell" frame type instead of falling back to super.shell() (plain terminal). setShellFrameCommand() handles kernel state — shows "Kernel not running" placeholder when kernel is down, auto-connects when kernel starts.
  • Bug 8 — Leaked store listeners on editor close:

    • projects.on("change") and store.on("change") listeners registered by watchJupyterStore were never removed when JupyterEditorActions was closed, causing leaked references and stale syncShellFrames() calls. Fixed by storing a cleanup function and calling it in close().
  • Bug 9 — terminal() action reused cleared shell frames:

    • terminal() called _get_most_recent_shell_id() which matched shell frames with command === undefined (kernel-not-running state). Switched to _get_most_recent_terminal_id() to only match actual terminal-type frames.

Test plan

  • Open a Jupyter notebook, execute a cell to start the kernel
  • Open console from View > Console — terminal should render and be interactive, title bar should say "Jupyter Console" (not "Terminal")
  • Open console from the frame type selector ("+" button or App menu) — "Jupyter Console" should appear as an option and work when selected
  • Verify that a regular Terminal can still be opened separately from the frame type selector
  • Switch a Terminal frame to Jupyter Console and back — each should get a fresh process
  • Restart the kernel — existing Jupyter Console frames should start a fresh session (In [1]:), not show old history
  • Kernel not running → View > Console → shows "Kernel not running" message (not a plain terminal)
  • Then start the kernel (run a cell) → the console should automatically connect
  • Stop the kernel → console should show "Kernel not running" message again
  • Close a Jupyter Console frame, then reopen — should reconnect cleanly
  • Same notebook in two tabs, both with console — close console in one tab, other tab stays alive
  • Open/close notebook tab 5-10 times — no growing console errors in devtools
  • Verify other editors (e.g. Python files) still open shells correctly via View > Shell

🤖 Generated with Claude Code

…selector

Bug 1: The jupyter console (View > Console) opened a panel showing
"jupyter console --existing PATH" but the terminal never rendered.
Root cause: frame tree args stored as Immutable.js Lists failed
MsgPack serialization over conat, so the backend received garbled
args and could not spawn the jupyter console process.  Fix: add
normalizeArgs() to convert Immutable Lists to plain string arrays.

Bug 2: The jupyter console was only accessible from the View menu,
not the frame type selector (+ button).  It was also mis-classified
as "Terminal" in the title bar.  Fix: introduce a dedicated "shell"
frame type in the jupyter EDITOR_SPEC, override shell()/new_frame()/
set_frame_type() in JupyterEditorActions to create "shell" frames
with the correct jupyter console command and args.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@haraldschilly haraldschilly added the PR-TODO-cocalc2 merge/migrate this PR into CoCalc2 in the future label Feb 19, 2026
@haraldschilly
Copy link
Contributor Author

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a4fe6c731e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

haraldschilly and others added 3 commits February 19, 2026 14:13
Address codex review comments and discovered issues:

- clear() action now handles type==="shell" (was throwing "not implemented")
- Shell frames reinitialize when command/args change (kernel restart,
  frame type switch) via a new useEffect in TerminalFrame watching both
  command and stringified args
- watchJupyterStore updates ALL shell frames on connection_file change,
  not just the most recent one
- set_frame_type handles shell→terminal transition by clearing command/args
- setShellFrameCommand uses close_terminal + set_frame_tree instead of
  set_command (which the backend doesn't implement)
- Hidden frames clear stale terminal refs so visibility effect can reinit

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…d kernel-aware shell frames

Backend (project/conat/terminal/manager.ts):
- Store CreateTerminalOptions in a persistent map instead of a closure
  variable so options survive service reuse and can be updated.
- createTerminalService now reapplies options (via createTerminal) even
  when the service already exists, so reconnecting with a new
  connection_file starts a fresh session.
- kill handler uses closeTerminal() to properly remove session, service,
  and options entries — prevents zombie sessions that caused blank
  terminals on reconnect.

Frontend (jupyter-editor/actions.ts):
- watchJupyterStore now watches both backend_state and connection_file.
  connection_file alone can be stale after kernel stops.
- New hasLiveKernelConnection() gate requires project running +
  backend_state==="running" + connection_file present.
- setShellFrameCommand falls back to clearShellFrameCommand when kernel
  is not live, putting the frame into "no command" mode.
- get_shell_spec gates on hasLiveKernelConnection so the frame type
  selector doesn't offer stale console connections.
- Removed fire-and-forget conn_write({ cmd: "kill" }) that raced with
  close_terminal.

Frontend (terminal-editor/terminal.tsx):
- Show "Kernel not running" message for shell frames without a command
  instead of a blank terminal or connection error.
- init_terminal early-returns when shellNeedsKernel is true.
- useEffect deps include frameType so shell↔terminal transitions trigger
  reinit, and no longer early-returns when terminalRef is null so
  "needs kernel" → "has command" transitions properly initialize.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@haraldschilly
Copy link
Contributor Author

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a799ef2087

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

haraldschilly and others added 3 commits February 19, 2026 17:02
… project state

- ConnectedTerminal.close() now sends api.kill() for jupyter-console
  terminals before disconnecting, preventing stale session resurrection
  when reopening the same console frame.
- watchJupyterStore also watches projects store for project run-state
  changes, so shell frames are resynced when the project stops/starts.
- Initial syncShellFrames() call on setup ensures page reload
  immediately reconciles shell frames with current kernel/project state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
terminal() was calling _get_most_recent_shell_id() which matches shell
frames with command===undefined (kernel-not-running state). Switch to
_get_most_recent_terminal_id() so only actual terminal-type frames are
reused, not shell placeholders.

Fixes codex review comment on PR #8750.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@haraldschilly
Copy link
Contributor Author

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 283264337e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@williamstein
Copy link
Contributor

I fixed this in cocalc-ai in these two commits so no need to tag this as needed to be rebased.

@williamstein williamstein removed the PR-TODO-cocalc2 merge/migrate this PR into CoCalc2 in the future label Feb 19, 2026
…close-kill

- shell() now always creates/reuses a "shell" frame type instead of
  falling back to a plain terminal when kernel is not running.
  setShellFrameCommand handles kernel state, showing "Kernel not running"
  placeholder instead of opening bash.
- Remove api.kill() from ConnectedTerminal.close() — killing the backend
  session on every frame close destroys shared sessions in multi-tab and
  collaborative scenarios. Backend createTerminal already handles stale
  sessions via options comparison.
- Clean up projects/jupyter store listeners on JupyterEditorActions.close()
  to prevent leaked references and stale syncShellFrames calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@haraldschilly
Copy link
Contributor Author

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 84a68c6dde

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@haraldschilly haraldschilly merged commit 23b101e into master Feb 20, 2026
3 checks passed
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.

2 participants

Comments