Workspace manager - design proposal

Design B selected by the Star Colonel on 2026-10-23 and refined to his notes. No code of the panel exists yet. The functional requirements are acceptance criteria in docs/acc-crit.md.

The extension replaces the built-in panel "Running Terminals and Kernels" with a panel built on workspaces. Each JupyterLab workspace is a card: its colour, purpose, age, counts and actions in the header, then its tabs, terminals and kernels when the card is open. The dark models use the token values of the live lab's theme, Galaxa Dark Theme - Steel; the light models use stock JupyterLab Light.

1. The panel it replaces

Built-in Running panel of the live lab, 2026-10-23

Screenshot of the live lab on 2026-10-23 (JupyterLab 4.6.4), taken in a scratch workspace that was deleted afterwards.

  • Seven sections in one column: Open Tabs, Kernels, Language servers, Recently Closed, Workspaces, Terminals, Launcher application
  • The 16 terminals of this server read terminals/1 to terminals/16; none shows the program that runs in it or when it was last used
  • Workspaces are a flat list of names; nothing shows which terminal or kernel belongs to which workspace
  • Sidebar icon today: current icon (a circle with a square); the extension brings its own, section 8

2. Decided by the Star Colonel

3. Where the data comes from

The five open questions of the brief, each answered from the JupyterLab 4.6.4 source and from probes on this server.

QuestionAnswerEvidence
How is a terminal or a kernel assigned to a workspace? Where does one show that belongs to no workspace, or to two?
  • A terminal belongs to a workspace when the workspace's saved layout holds the key terminal:<name>
  • A kernel belongs to a workspace when a notebook or console tab of that workspace has a session on that kernel (GET /api/sessions: session path → kernel id)
  • For the workspace of this window the panel reads the live tabs of the window, not the saved file
  • In two workspaces: the row appears in both sections with the mark "also in <name>", and each count includes it
  • In no workspace: the section "In no workspace" after the last workspace, which lists the terminals, kernels and language servers that belong to none
16 terminals on this server: workspace default holds 5 (7, 11, 13, 14, 15), auto-6 holds 1 (1), 10 are in no workspace. The culler sibling reads the same terminal:<name> keys to protect terminals.
Where does the "last accessed" time of a workspace come from?
  • metadata.last_modified of the workspace, which jupyterlab_server reads from the file's modification time
  • JupyterLab writes the workspace 500 ms after any state change of a window that shows it, and once when a window opens it
  • The age in the header is therefore the time since a window last opened or changed the workspace
  • Limit: a window left open with no change does not move the time
Probe 2026-10-23: a workspace with its time set 2 days back was opened in a headless browser; with no click, its last_modified became the load time. Source: apputils-extension state plugin, a 500 ms debouncer around workspaces.save. The culler deletes a workspace when this same time is older than its idle timeout (7 days by default; never default), so the age is also the culler's clock.
Where do the last activity time and the process name of a terminal come from?
  • Last activity: last_activity of GET /api/terminals; jupyter_server_terminals sets it on each output read and each input message
  • Process name: a new endpoint of the server extension reads the foreground process group of the terminal's pty (os.tcgetpgrp on the pty, then /proc/<pgid>/comm)
  • The tab name is the title the program sets with the OSC 0 or 2 escape; JupyterLab copies it into the tab label and never stores it, so only a window that shows the terminal knows it
  • The row shows the process name for every terminal, and the tab title as the row's tooltip when this window shows that terminal
The REST model of a terminal is {name, last_activity} only. Probe 2026-10-23: the foreground process of each terminal shell of this server reads claude or fish from /proc. Source: terminal/src/widget.ts, onTitleChange sets title.label.
How are the tabs of a workspace that is not the current one read and closed?
  • Read: GET /lab/api/workspaces/<id>; data["layout-restorer:data"].main.dock lists the tab keys (terminal:7, notebook:<path>, editor:<path>)
  • Close all: the extension removes those keys from the layout and from data, and writes the workspace back with PUT; a terminal or kernel keeps running
  • A browser window that has that workspace open keeps its own copy and writes it back on its next change. Each window reports its workspace to the server extension every 30 s, so the panel knows which workspaces are open elsewhere and refuses Close all tabs, Delete workspace, Open workspace (the header click, the header icon and the menu item), Open in new browser tab and a row click for them, naming the reason
  • Open: this window navigates to /lab/workspaces/<id> and JupyterLab loads that workspace with its tabs; the menu also offers "Open in new browser tab"
Saved layout of default on 2026-10-23: main.dock.widgets = terminal:14, terminal:11, terminal:13, terminal:15, terminal:7, each with a data entry {"name": "<n>"}. The built-in Workspaces section reads the tab count the same way.
Replace the built-in panel or add a panel?
  • Decided: replace
  • package.json disables only @jupyterlab/running-extension:sidebar (jupyterlab.disabledExtensions); @jupyterlab/running-extension:plugin stays, so other extensions still register their sections
  • The new panel provides IRunningSessionSidebar, keeps the id jp-running-sessions and the restorer name running-sessions (saved layouts open it in the same place) and registers running:show-panel again
  • Sections that other extensions add (here: Recently Closed, Launcher application) are listed under "Other sessions", each item with the actions the replaced panel gave it: a shutdown button, its child rows, and Shut Down All with its confirmation where the section offers it; Language servers is not one of them, its rows are listed in In no workspace
Source of running-extension 4.6.4: the sidebar is its own plugin, restorer.add(running, 'running-sessions'); jupyterlab_server reads disabledExtensions from an extension's package.json (config.py, lines 170 to 173).

4. Design B, refined

At load: no card is open

WORKSPACES(11)defaultnowExtension development153iconprobe28h001auto-6Firmware upload tests010wf-regression-hist-13dRegression history work…102fb-exp-panels-base4d000In no workspace010Other sessions(2)3 hidden workspaces
Dark
WORKSPACES(11)defaultnowExtension development153iconprobe28h001auto-6Firmware upload tests010wf-regression-hist-13dRegression history work…102fb-exp-panels-base4d000In no workspace010Other sessions(2)3 hidden workspaces
Light

After a click on the card of this window's workspace

WORKSPACES(11)defaultnowExtension development153TABS(3)README.mddocsbrief.mddocsdesign.ipynbnotebooksTERMINALS(5)claudeterminal 7nowclaudeterminal 112mfishterminal 133hclaudeterminal 1412mfishterminal 151dKERNELS(1)Python 3design.ipynb5miconprobe28h001auto-63dFirmware upload tests010wf-regression-hist-13dRegression history work…102In no workspace010Other sessions(2)3 hidden workspaces
Dark
WORKSPACES(11)defaultnowExtension development153TABS(3)README.mddocsbrief.mddocsdesign.ipynbnotebooksTERMINALS(5)claudeterminal 7nowclaudeterminal 112mfishterminal 133hclaudeterminal 1412mfishterminal 151dKERNELS(1)Python 3design.ipynb5miconprobe28h001auto-63dFirmware upload tests010wf-regression-hist-13dRegression history work…102In no workspace010Other sessions(2)3 hidden workspaces
Light

Icons

Tab counttabIconTerminalown glyphKernel / notebookkernelIconkernel idlekernel busy, or active under 60 skernel state unknownclosed / open; triangle toggles, header switchesthis window's workspace
Dark
Tab counttabIconTerminalown glyphKernel / notebookkernelIconkernel idlekernel busy, or active under 60 skernel state unknownclosed / open; triangle toggles, header switchesthis window's workspace
Light

Legibility

Measured as WCAG contrast ratio of text to its background; WCAG AA asks 4.5:1 for text under 18px. Galaxa Dark Theme - Steel sets --jp-ui-font-size1 to 11px and its colour 2 (#7d8791) reads 3.3:1 even on the plain panel, so the refined cards keep colour 2 out of every text. Each value is the lowest of the six workspace colours.

TextFirst draft of BRefinedGalaxa SteelLight
Workspace name12px, colour 1, 12% tint--jp-ui-font-size2 (13.2px), semibold, colour 0, 8% tint3.7 → 5.614.8 → 19.8
Purpose11px italic, colour 2size 1 (11px), regular, colour 02.5 → 5.64.2 → 19.8
Age and counts11px, colour 2size 1, colour 0; glyphs colour 12.5 → 5.64.2 → 19.8
Header of this window's workspacecolour 2 on 22% tint (open card)colour 0 on the active shade2.0 → 6.74.0 → 9.9
Row detail (terminal name, notebook)11px, colour 2size 1, colour 1 on a neutral body3.3 → 4.84.6 → 16.1
Row detail in this window's open cardcolour 2colour 1 on the body (40% of the active shade)3.3 → 4.84.6 → 12.6
Row detail on a hovered rowcolour 2 on layout 2colour 0 under a 12% veil of the text colour2.4 → 5.24.0 → 7.7
"now"brand colour 1colour 0, semibold, plus a brand colour 2 dot3.8 → 5.64.6 → 19.8

5. Behaviour common to the panel

Panel header

New workspace

New workspaceNamefb-reviewLetters, digits, - and _Short description (optional)Review of the feedback runs beforethe release of the new grader64/100CreateCancelNew workspaceNameauto-6A workspace named auto-6 existsShort description (optional)0/100CreateCancel
Dark
New workspaceNamefb-reviewLetters, digits, - and _Short description (optional)Review of the feedback runs beforethe release of the new grader64/100CreateCancelNew workspaceNameauto-6A workspace named auto-6 existsShort description (optional)0/100CreateCancel
Light

Filter

WORKSPACES(11)claudexdefaultnowExtension development(3 of 9)TERMINALS(3)claudeterminal 7nowclaudeterminal 112mclaudeterminal 1412mauto-63dFirmware upload tests(1 of 1)TERMINALS(1)claudeterminal 11dIn no workspace(4 of 10)4 workspaces without a match are not shown
Dark
WORKSPACES(11)claudexdefaultnowExtension development(3 of 9)TERMINALS(3)claudeterminal 7nowclaudeterminal 112mclaudeterminal 1412mauto-63dFirmware upload tests(1 of 1)TERMINALS(1)claudeterminal 11dIn no workspace(4 of 10)4 workspaces without a match are not shown
Light

Workspace menu

WORKSPACES(11)auto-63dFirmware upload tests010Open workspaceOpen in new browser tabClose all tabsSet description...Colour>Hide workspaceDelete workspaceRedOrangeYellowGreenBluePurple
Dark
WORKSPACES(11)auto-63dFirmware upload tests010Open workspaceOpen in new browser tabClose all tabsSet description...Colour>Hide workspaceDelete workspaceRedOrangeYellowGreenBluePurple
Light

Candidates for the Open workspace icon, each as a hover button beside close-all-tabs and enlarged. Decided by the Star Colonel: 2, the arrow in a circle. An arrow into a box read as exit, and two opposite arrows were rejected.

1. Open folder2. Arrow in a circle3. Panes4. Layout
Dark
1. Open folder2. Arrow in a circle3. Panes4. Layout
Light

Hidden workspaces

WORKSPACES(11)defaultnowExtension development153auto-63dFirmware upload tests010iconprobehiddenfb-exp-rename-basehiddenfb-exp-simcheckhiddenThe eye button shows a hidden workspace again
Dark
WORKSPACES(11)defaultnowExtension development153auto-63dFirmware upload tests010iconprobehiddenfb-exp-rename-basehiddenfb-exp-simcheckhiddenThe eye button shows a hidden workspace again
Light

Rules of the cards

6. Workspace colours

1. rosestripheader 8%selected headerselected body2. peachstripheader 8%selected headerselected body3. lemonstripheader 8%selected headerselected body4. mintstripheader 8%selected headerselected body5. skystripheader 8%selected headerselected body6. lavenderstripheader 8%selected headerselected bodyOrder of rotation: 1 to 6, then 1 again. Values follow the colourful tab palette (see the page).
Dark
1. rosestripheader 8%selected headerselected body2. peachstripheader 8%selected headerselected body3. lemonstripheader 8%selected headerselected body4. mintstripheader 8%selected headerselected body5. skystripheader 8%selected headerselected body6. lavenderstripheader 8%selected headerselected bodyOrder of rotation: 1 to 6, then 1 again. Values follow the colourful tab palette (see the page).
Light

7. Working with the culler sibling

8. Sidebar icon

1. Layers2. Strip window3. Accordion4. PanesLeft: 16px, as on the sidebar tab. Right: enlarged. One colour (jp-icon3).
Dark
1. Layers2. Strip window3. Accordion4. PanesLeft: 16px, as on the sidebar tab. Right: enlarged. One colour (jp-icon3).
Light

9. Choices, all decided on 2026-10-23

ChoiceDecision
1. Visual designB, tinted cards, refined
2. Filterevery card with a match is open while the filter is active (recommendation, accepted with "ok")
3. Countstabs, terminals, notebooks in each card header; the number of cards in the panel header
4. Tab listeach tab once (recommendation, accepted with "ok")
5. Recordsthe extension's own file beside the workspaces (recommendation, accepted with "ok")
6. Coloursgiven automatically from the extension's own palette, which starts from the colourful tab's active tab shades; the user changes them in the workspace menu
7. Sidebar icon4, four panes
8. Hide this window's workspacenot possible (recommendation, accepted with "ok")
9. Clicks on a cardthe triangle opens or closes it; the header of another workspace switches to that workspace

10. Acceptance criteria

The criteria live in docs/acc-crit.md, written through pm-tools as @kj: 169 in 9 categories, none open, 161 closed and 8 rejected. Importance: 14 CRITICAL, 57 HIGH, 76 MEDIUM, 22 LOW. Each names its test: 57 with a Jest or pytest test, 128 with a Galata test (a criterion with both counts twice). pm-tools report docs/acc-crit.md prints the status.

11. Added while filling the criteria

A complete replacement of the Running panel needs these; they are filed as criteria and wait for the Star Colonel's veto.

Appendix - the alternatives compared on 2026-10-23

The first drafts, kept as the record of the choice; they are not maintained.

A. Accordion with a colour strip

WORKSPACES11defaultcurrentExtension developmentnow153TABS3README.mddocsbrief.mddocsdesign.ipynbnotebooksTERMINALS5claudeterminal 7nowclaudeterminal 112mfishterminal 133hclaudeterminal 1412mfishterminal 151dKERNELS1Python 3design.ipynb5miconprobe28h001auto-63dFirmware upload tests010wf-regression-hist-13dRegression history workflow102fb-exp-panels-base4d000In no workspace010Other sessions3 sections3 hidden workspaces
Dark
WORKSPACES11defaultcurrentExtension developmentnow153TABS3README.mddocsbrief.mddocsdesign.ipynbnotebooksTERMINALS5claudeterminal 7nowclaudeterminal 112mfishterminal 133hclaudeterminal 1412mfishterminal 151dKERNELS1Python 3design.ipynb5miconprobe28h001auto-63dFirmware upload tests010wf-regression-hist-13dRegression history workflow102fb-exp-panels-base4d000In no workspace010Other sessions3 sections3 hidden workspaces
Light

B. Tinted cards, first draft (selected)

WORKSPACES11defaultnowExtension development153README.mddocsbrief.mddocsdesign.ipynbnotebooksclaudeterminal 7nowclaudeterminal 112mfishterminal 133hclaudeterminal 1412mfishterminal 151dPython 3design.ipynb5mClose all tabscurrent workspaceiconprobe28h001auto-63dFirmware upload tests010wf-regression-hist-13dRegression history workflow102fb-exp-panels-base4d000In no workspace010Other sessions3 sections3 hidden workspaces
Dark
WORKSPACES11defaultnowExtension development153README.mddocsbrief.mddocsdesign.ipynbnotebooksclaudeterminal 7nowclaudeterminal 112mfishterminal 133hclaudeterminal 1412mfishterminal 151dPython 3design.ipynb5mClose all tabscurrent workspaceiconprobe28h001auto-63dFirmware upload tests010wf-regression-hist-13dRegression history workflow102fb-exp-panels-base4d000In no workspace010Other sessions3 sections3 hidden workspaces
Light

C. Colour rail

WORKSPACES11DIA6WRFE3defaultcurrentExtension developmentused now153OpenClose all tabsTABS3README.mdbrief.mddesign.ipynbTERMINALS5claude 7nowclaude 112mfish 133hclaude 1412mfish 151dKERNELS1Python 35m
Dark
WORKSPACES11DIA6WRFE3defaultcurrentExtension developmentused now153OpenClose all tabsTABS3README.mdbrief.mddesign.ipynbTERMINALS5claude 7nowclaude 112mfish 133hclaude 1412mfish 151dKERNELS1Python 35m
Light