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.
1. The panel it replaces
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/1toterminals/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:
(a circle with a square); the extension brings its own, section 8
2. Decided by the Star Colonel
- The extension replaces the built-in panel completely ("our extension completely replaces the old one")
- Design B, the tinted cards, is the starting point ("design B - refine it, we will start from this")
- This window's workspace is not expanded by default ("selected workspace would not be expanded by default")
- Three counts in the card header - tabs, terminals, notebooks - each with its own icon; the terminal icon stays, tabs and notebooks get new ones ("tabs must have different icon; terminals ok; notebooks is ok, but must use different icon for that (kernel?)")
- Better legibility ("improve legibility")
- This window's workspace carries no "current" mark; a
--jp-brand-color2outline goes around its card ("selected will not be 'current', just brand 2 outline around it") - Every card has an open and close triangle ("there must be open / close triangle for the workspace")
- The card of this window's workspace has a stronger background colour ("the bg colour must get stronger too for the selected one")
- Sidebar icon 4, the four panes ("icon - 4 panels")
- Colours are given automatically and the user can change them ("autocolours, but user can change"); the extension's own palette, not the colourful tab's variables
- The stronger tint is the hint that marks the selected workspace; the edge colours stay ("it is about hinting with stronger tint that this is the selected workspace")
- The hint is as strong as the coloured tabs make it between an inactive and an active tab ("tint is not strong enough; look at the coloured tabs - that we make a difference between inactive vs active tab")
- Only this window's card is strong; the other cards stay as they were, and the card's body is weaker than its header ("only selected one strong, keep unselected ones as you had them before; and the selected one is too strong now, keep that strength for the workspace header")
- The colour strip of this window's card looks as on every other card ("keep the edge coloured tab same style as the inactive ones")
- The card's parts fit exactly ("must do perfect fit")
- No spacing between the outline and the card, nor between the strip and the background ("outline must fit perfectly, those spacings that I shown must not be")
- The remaining recommendations stand ("ok, start filling up acc crits"): section 9
- Each workspace has a toggle "hidden" with an eye icon; a "..." button in the header opens the workspace's menu
- The panel has a refresh button and a filter that finds anything, the same concept as the filter of
jupyterlab_ai_code_assistants_extension - A + button adds a new workspace and asks for its name and an optional description, which is the purpose shown on the card ("and + for adding new workspace; asks for name and description (optional)")
- The description is short, at most 100 characters, in a larger field with a character counter ("also update design to show short description (100 characters max)", "larger input box for desc", "char counter - 45/100")
- Every item of the workspace menu has an icon ("and menu must have icons")
- The colour swatches look like those of the coloured tabs ("colour swatvches - same style as for tabds in coloured tabs extension")
- The open icon is candidate 2, the arrow in a circle ("open looks like exit (icon)", "different icon", then "ok" to the recommendation)
- A new sidebar icon replaces the old one
- The functional requirements are written as acceptance criteria and defects through pm-tools, author
@kj
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.
| Question | Answer | Evidence |
|---|---|---|
| How is a terminal or a kernel assigned to a workspace? Where does one show that belongs to no workspace, or to two? |
|
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? |
|
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? |
|
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? |
|
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? |
|
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
{{svg:b2-closed}}After a click on the card of this window's workspace
{{svg:b2-open}}Icons
{{svg:b2-legend}}- No card open at load, the card of this window's workspace included; a click on a triangle opens a card and closes the one that was open
- Triangle before every name: pointing right on a closed card, down on the open one. A click on the triangle opens or closes that card and never switches this window; a click on the header of another workspace's card, outside its triangle and its buttons, switches this window to that workspace; a click on the header of this window's card, of In no workspace or of Other sessions opens or closes that card
- This window's workspace has a 2px
--jp-brand-color2outline lying against its card's edge. Its header takes the active shade of its colour, the shadejupyterlab_colourful_tab_extensiongives an active tab, and its body a 40% mix of that shade into the panel colour, so the header is the strongest part. Every other card header keeps its 8% tint. The colour strip is the same on every card and meets the background directly. It is also the first card - Fit: each card is one rounded shape (3px radius) that clips everything inside it - the strip and the backgrounds are square layers, so the strip follows the corners and no seam or notch shows; the outline lies against the card's edge and follows its corners. In CSS:
border-radius: 3px; overflow: hiddenon the card andoutline: 2px solid var(--jp-brand-color2); outline-offset: 0, which follows the radius - In Light the strip has the colour of this window's active header, so on that card it shows against the paler body only
- Tabs count uses JupyterLab's own
tabIcon(a window with a tab); the rows under Tabs keep the file-type icon of each document, as JupyterLab's tabs do - Notebooks count and kernel rows use JupyterLab's own
kernelIcon(the chip of the status bar); the count is the number of kernels of the workspace's notebooks and consoles; a kernel row shows the display name of its kernelspec, with the kernelspec id in its tooltip - Terminal glyph stays as approved; it is the extension's own outline glyph, because JupyterLab's
terminalIconis a filled block that draws heavier than the two others - Kernel state is a dot before the time:
--jp-success-color1idle,--jp-brand-color2busy,--jp-layout-color4unknown - Card actions: a closed card shows open (an arrow in a circle; the candidates are under Workspace menu) and close-all-tabs as icons on hover; while they show they take the place of the age, so the name keeps its width; Open is disabled on this window's workspace. An open card ends with its last row and shows no Open or Close all tabs button; both actions stay in its ... menu
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.
| Text | First draft of B | Refined | Galaxa Steel | Light |
|---|---|---|---|---|
| Workspace name | 12px, colour 1, 12% tint | --jp-ui-font-size2 (13.2px), semibold, colour 0, 8% tint | 3.7 → 5.6 | 14.8 → 19.8 |
| Purpose | 11px italic, colour 2 | size 1 (11px), regular, colour 0 | 2.5 → 5.6 | 4.2 → 19.8 |
| Age and counts | 11px, colour 2 | size 1, colour 0; glyphs colour 1 | 2.5 → 5.6 | 4.2 → 19.8 |
| Header of this window's workspace | colour 2 on 22% tint (open card) | colour 0 on the active shade | 2.0 → 6.7 | 4.0 → 9.9 |
| Row detail (terminal name, notebook) | 11px, colour 2 | size 1, colour 1 on a neutral body | 3.3 → 4.8 | 4.6 → 16.1 |
| Row detail in this window's open card | colour 2 | colour 1 on the body (40% of the active shade) | 3.3 → 4.8 | 4.6 → 12.6 |
| Row detail on a hovered row | colour 2 on layout 2 | colour 0 under a 12% veil of the text colour | 2.4 → 5.2 | 4.0 → 7.7 |
| "now" | brand colour 1 | colour 0, semibold, plus a brand colour 2 dot | 3.8 → 5.6 | 4.6 → 19.8 |
- No italics anywhere; no text below size 1 except the group labels (size 0, uppercase, semibold)
- The brand colour 2 outline reads 5.4:1 against the panel in Galaxa Steel but 2.65:1 in Light, under the 3:1 WCAG asks for a graphic that carries meaning; in Light the card's place at the top of the list carries the same fact
- Every other card header keeps an 8% tint of its colour. This window's header takes the active tab shade, its body a 40% mix of it: in Light the body is a paler form of the header; in Galaxa Steel the active shades sit at the panel's own brightness and differ from it by hue, as on the tabs
- The body of another open card stays neutral (
--jp-layout-color1) - A hovered row lays a 12% veil of the text colour over its background, so it reads on a neutral body and on the active shade alike
- Buttons are filled with
--jp-layout-color2: Galaxa Steel's border colour 2 is the same#404b54, which left the outlined buttons of the first draft at 1.4:1 to the panel - Brand colour 2 alone would give "now" 2.65:1 in Light and brand colour 1 3.8:1 in Galaxa Steel, so the word is colour 0 and the colour moves to the dot, the activity marker of the design language
5. Behaviour common to the panel
Panel header
- Title "WORKSPACES" and the number of workspaces shown in brackets: WORKSPACES (4)
- Four 24px buttons, in this order: new workspace (+), filter (shows or hides the filter field), show hidden (eye; shows the hidden workspaces), refresh. No Search tabs button sits in the header; the command stays in the command palette
- Refresh reloads workspaces, terminals, sessions and process names; its icon turns for at least 500 ms and the button is disabled until the requests return
- While the panel is visible it reloads by itself every 10 s, without the turning icon
New workspace
{{svg:b2-new}}- The + button opens the dialog "New workspace": Name, with the keyboard focus, and Short description (optional), the purpose the card shows; Enter creates, Escape closes without a change
- The name follows JupyterLab's own rule for workspace names: letters, digits,
-and_, because the name becomes the address/lab/workspaces/<name>. Create stays disabled until the name follows the rule - A name that a workspace already has is refused. JupyterLab's own Create New Workspace saves over such a workspace and its layout is lost; the dialog checks the name against a fresh list of workspaces before it saves
- The short description is a box of three lines that wraps the text, with a counter under it (
64/100) that changes with every keystroke. It holds at most 100 characters and no line break, so Enter still creates; it is shown as plain text; left empty, none is stored. Set description... in the workspace menu uses the same field - Create saves an empty layout through JupyterLab's workspaces API and stores the purpose and the next colour of the rotation in the extension's records file. The new card is second in the list, after this window's card, open and with the age "now"
- This window stays in its workspace; Open workspace or Open in new browser tab in the new card's menu goes to the new workspace
- The dialog is JupyterLab's own Dialog, so its buttons take the theme's colours. An error is a line of colour 0 after a dot in the error colour, because the error colour reads 3.2:1 as text in Galaxa Steel
Filter
{{svg:b2-filter}}- As in the AI assistants panel: the filter button shows a field under the header; hiding the field clears the filter; a clear button sits inside the field
- The match is the AI panel's: case, accents and separators (
- _ . /and spaces) are ignored, then a substring match with a 5% edit tolerance for queries of 10 or more characters - Searched: workspace name, purpose, tab names and paths, process names, terminal names and tab titles, kernel display names and kernelspec ids and their notebooks
- Only rows that match are shown, and a workspace without a match is not shown; the card header reads "(N of M)"
- While a filter is active, every card with a match is open; a click on a card's triangle then opens or closes that card only, and the filter stays; the open card of before returns when the filter is cleared
Workspace menu
{{svg:b2-menu}}- Items: Open workspace, Open in new browser tab, Close all tabs, Set description..., Colour (the six colours), Hide workspace, Delete workspace
- Each item has a 16px icon before its label: Open workspace an arrow in a circle, the same icon as on the card header; Open in new browser tab an arrow out of a box; Close all tabs the card header's icon; Set description a pencil; Colour the swatch of the workspace's colour; Hide workspace an eye with a stroke; Delete workspace a bin
- Only the bin takes the error colour, and the warning icon of an armed Confirm delete in its place; the label Delete workspace stays colour 0, because the error colour reads 3.2:1 as text in Galaxa Steel
- The Colour submenu matches the Tab Colour menu of
jupyterlab_colourful_tab_extension: before each name - Red, Orange, Yellow, Green, Blue, Purple - a 12px square swatch with 2px corners and a 1px grey border. The border is--jp-layout-color4where the tab extension writes#888, because this extension styles through JupyterLab variables only. The swatch takes the strip colour, so it shows the colour the card will carry - Delete workspace is the design system's two-step action: the first click arms "Confirm delete", showing a warning icon in the error colour in place of the bin, and the second click deletes; at rest, armed and active the label keeps the menu's normal text colour and the row the normal selection background; it never runs on this window's workspace
- Close all tabs asks once when the workspace holds terminals, because the culler may then shut those terminals down after its idle time
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.
{{svg:open-icons}}Hidden workspaces
{{svg:b2-hidden}}- Hide workspace removes the card from the panel; the line under the last card reads "N hidden workspaces"
- The eye button in the header shows the hidden workspaces among the others, with the word "hidden" and an eye button that shows the workspace again
- The terminals and kernels of a hidden workspace are not moved to "In no workspace"; they stay hidden with it
- This window's workspace is never hidden
Rules of the cards
- At most one card is open; opening a card closes the open one; no card is open at load
- A click on a card's triangle opens or closes that card; a click on the header of another workspace's card switches this window to that workspace; a click on the header of this window's card, of In no workspace or of Other sessions opens or closes it
- Order: this window's workspace first, then the others by age, newest first; then "In no workspace", then "Other sessions"
- Each tab is listed once: documents and other widgets under Tabs, terminal tabs under Terminals, the kernels of notebook and console tabs under Kernels
- A row click opens the item: a document or terminal of this window's workspace is activated; one of another workspace opens that workspace, unless another window shows it
- Ages use the design system's labels:
now,Nm,Nh,Nd
6. Workspace colours
{{svg:palette}}- Six colours in the order of
jupyterlab_colourful_tab_extension: rose, peach, lemon, mint, sky, lavender - A new workspace takes the next colour after the last one given; the colour is stored, so deleting a workspace does not move the colours of the others; the menu changes it
- Each colour has an active shade, the value
jupyterlab_colourful_tab_extensiongives an active tab, the same as in this lab's settings. Light: rose#e39fb1... sky#77bae0. Galaxa Steel and dark themes: rose#3a2328... sky#1d2a38. It colours only the header of this window's card and, mixed 40% into the panel, its body; other headers take an 8% tint of the strip colour - The 4px strip keeps the brighter values (light theme: the active shade; dark theme: the light pastel), because the dark shades almost disappear as a thin line on a dark panel
- The palette is the one place where the extension defines colour values, as the colourful tab extension does; everything else is a
--jp-*token
7. Working with the culler sibling
- The age shown is the culler's own clock (
last_modified), so a workspace near its idle timeout reads as old in the panel - Purpose, colour and hidden state are not written into the workspace file: a write there would reset the culler's clock and would race with a window that has the workspace open. They live in the extension's own file beside the workspaces
- Close all tabs on another workspace removes its
terminal:<name>keys; the culler then treats those terminals as in no workspace - A workspace, terminal or kernel the culler removes leaves the panel at the next reload; the extension drops the stored record of a deleted workspace
- A new workspace that nobody opens ages like any other: the culler deletes it after its workspace idle time (7 days by default)
- The extension shuts down nothing by itself; every shutdown and every delete is a click of the user
8. Sidebar icon
{{svg:icons}}- Four candidates, drawn in one colour as the design system asks for the rail
- Decided by the Star Colonel: 4, four panes in a 2 by 2 grid with the top-left one filled
9. Choices, all decided on 2026-10-23
| Choice | Decision |
|---|---|
| 1. Visual design | B, tinted cards, refined |
| 2. Filter | every card with a match is open while the filter is active (recommendation, accepted with "ok") |
| 3. Counts | tabs, terminals, notebooks in each card header; the number of cards in the panel header |
| 4. Tab list | each tab once (recommendation, accepted with "ok") |
| 5. Records | the extension's own file beside the workspaces (recommendation, accepted with "ok") |
| 6. Colours | given 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 icon | 4, four panes |
| 8. Hide this window's workspace | not possible (recommendation, accepted with "ok") |
| 9. Clicks on a card | the 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.
- Rejected and replaced:
ACC-ACCORD-12-START-OPENbyACC-ACCORD-68-START-CLOSED;ACC-ACCORD-14-CURRENTbyACC-ACCORD-76-OUTLINE;ACC-ACCORD-79-CURRENT-TINT(16% tint) byACC-ACCORD-124-CURRENT-SHADE(shades on every card), which in turn was replaced byACC-ACCORD-125-SELECTED-SHADE(only this window's card strong);ACC-ACCORD-126-STRIP-SAMEandACC-ACCORD-127-FIT(both with spacing) byACC-ACCORD-128-STRIP-PLAINandACC-ACCORD-129-FLUSH;ACC-ACCORD-21-AGE-NOW(brand colour 1) byACC-ACCORD-130-NOW-DOT;ACC-PANEL-136-HEADER-BUTTONS(three buttons) byACC-PANEL-147-HEADER-ORDER - Filed for the + button:
ACC-PANEL-140-NEW-BUTTONtoACC-PANEL-146-NEW-STAY- the button, the dialog, the name rule, an existing name refused, the purpose, the saved workspace, this window staying where it is - Changed for the short description: at most 100 characters in
ACC-ACCORD-100-PURPOSE-LENGTH,ACC-SERVER-119-META-VALIDATEandACC-PANEL-144-NEW-PURPOSE; the menu item Set description inACC-ACCORD-19,29and99; addedACC-ACCORD-148-MENU-ICONS,ACC-PANEL-149-DESC-COUNTandACC-ACCORD-150-COLOUR-SWATCH - Filed after an audit of this page against the criteria:
ACC-ACCORD-131-OPEN-BUTTONStoACC-FILTER-139-UNMATCHED- the buttons of an open card, the name size, other kernel states, group labels, the counts of In no workspace, the header button order, Other sessions closed at load, labels on icon buttons, the line of workspaces without a match - Filed for the decided choices:
ACC-PANEL-80-WS-COUNTonwards, among themACC-LISTS-81-ONCE,ACC-FILTER-82-OPEN-MATCHES,ACC-SERVER-84-META-FILE,ACC-CULLER-85-NO-MTIME,ACC-ACCORD-86-OWN-PALETTE,ACC-HIDDEN-87-NOT-CURRENT - Filed for the additions of section 11:
ACC-PANEL-88-STATUSBARtoACC-SERVER-123-NO-TERMINALS - Filed on 2026-10-24 for the findings of round 3 of the implementation review that were deferred:
ACC-PANEL-152-NARROW-LABELS,ACC-ACCORD-153-HOVER-NAME,ACC-LISTS-154-TAB-SHRINK,ACC-ACCORD-155-ARMED-COLOUR,ACC-KERNEL-156-DISPLAY-NAMEandACC-PANEL-157-OTHERS-ACTIONS- the labels whole at 250 px, the hover icons in place of the age, the text of a row that gives way first, the armed delete warning icon, the kernel display name, the Other sessions actions - Filed on the Star Colonel's word the same day:
ACC-LISTS-158-LANG-LISTED,ACC-PANEL-159-LANG-OTHERS,ACC-LISTS-160-LANG-ONLYandACC-LISTS-161-LANG-ACTIONS- language servers are listed in In no workspace, not under Other sessions - Filed on the Star Colonel's word the same evening:
ACC-ACCORD-162-TRIANGLEtoACC-PANEL-169-NO-SEARCH- the triangle and the header clicks, no buttons in an open card, counts in brackets, no Search tabs button in the header
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.
- Row actions in a context menu (right-click or the context-menu key), as the design system prescribes for rows that open something: close one tab, shut down a terminal, shut down a kernel; shutting down a terminal that another workspace holds asks first
- Shut down all in the ... menu of "In no workspace", asking once with the number of terminals and kernels; its Language servers group keeps the same row actions: a shutdown button and Shut Down All with its confirmation
- JupyterLab's own entry points keep working: the status bar item of running terminals and kernels opens the panel; the Search tabs command stays in the command palette
- Keyboard: Tab reaches a card's triangle and its header, Enter or Space on either does what a click does, arrow keys move between rows, a 1px brand colour 1 outline shows keyboard focus only
- Description dialog (Set description...): the three-line Short description field with its counter, focused, Enter saves; at most 100 characters; shown as plain text, never as markup
- Records file:
workspace-manager.jsonin the lab's workspaces directory; one workspace changed per request; written whole through a temporary file; an unreadable file is moved aside to.bad - Failures: without the server extension the panel still lists workspaces, terminals by name and kernels; a failed reload shows one line and keeps the last content; a filter without a match says so
- Long names end in an ellipsis with the full text in the tooltip; in a row too narrow for its text, a tab's folder shortens before its file name, while a terminal or kernel row shortens its also in <name> mark first, down to the words also in, then the label and the detail together, each keeping its first characters, and the age stays whole; the card list scrolls under a fixed header
- After Create this window stays in its workspace and the new card opens in the list. The other way is to switch this window to the new workspace at once
Appendix - the alternatives compared on 2026-10-23
The first drafts, kept as the record of the choice; they are not maintained.