Skip to content

Move the run buttons into the tab bar, add a host tab and compact the input - #1194

Merged
bmesuere merged 17 commits into
mainfrom
enh/compact-code-runner
Oct 13, 2026
Merged

bmesuere merged 17 commits into
mainfrom
enh/compact-code-runner

Conversation

@bmesuere

@bmesuere bmesuere commented Oct 11, 2026 •

Copy link
Copy Markdown
Member

Part of #1193: items 1, 2 and 4 (Run and Debug in the tab bar, a tab supplied by the host, a compact p-input), plus the narrow-width behaviour of these elements. The output panel, the debugger header and the container queries of the other panels (items 3, 5 and 6) are done in another branch.

What changed

Run and Debug in the tab bar (p-code-runner, p-button-lint). p-button-lint moved from below the editor pane into the row of p-editor-tabs. The row is 44px high and the buttons are 32px (--md-*-button-container-height). The 44px minimum is set by p-code-runner on its own tab bar; a p-editor-tabs elsewhere stays 36px. The buttons slot follows after a 1px divider, which only shows when the slot has content. Slotted buttons should be 32px high too; a taller one makes the whole row taller, and the tab bar grows with it so the two hairlines stay level. While debugging the row shows only Stop debugging: the slot and its divider are hidden. The add-file button also stays hidden while debugging, as before: adding files to a trace makes no sense, and the "+" in the render was a mockup inaccuracy. The demo app's example picker moved to the header, since an outlined select does not fit in a 44px toolbar. The header and its picker groups now wrap, so the header fits at 800px and at phone widths.

Narrow columns. The bar stays on one row as long as it can. p-code-runner measures the tabs and both forms of the buttons; when they do not fit, the run buttons (Run, Debug, Run doctests, Stop, Stop debugging) turn into 32px icon buttons with an aria-label and a tooltip. Below 30rem (p-code-runner is a size container named papyros-code-runner) they always are. When even the icons do not fit, for example with many file tabs, the labelled buttons wrap to a row of their own, and the divider never ends a row. The form that is not shown is laid out invisibly (visibility: hidden, absolutely positioned) rather than with display: none, so both forms can be measured; it is not focusable and not in the accessibility tree.

Slotted buttons come last: when the row still does not fit with icon run buttons, p-code-runner sets a compact-slotted attribute on itself, and the host compacts its own buttons with it. Papyros keeps the width of the slotted buttons with and without the attribute, so it does not flip back and forth. In Dodona's sandbox this keeps "Finalize and submit" labelled down to a code runner of about 400px, while Run and Debug are icons from about 500px:

p-code-runner[compact-slotted] .my-button-label { /* visually hidden */ }

Run doctests had the play icon of Run, so the two looked the same as icon buttons; it now has mdi-script-text-play. The run buttons after the first no longer get ref(undefined), which lit-html 3.3.2 (Dodona's version) throws on when a button is removed.

This overlaps with #581 (icon buttons on small screens) for the code bar.

A tab supplied by the host. p-code-runner takes two new slots:

  • host-tab: the tab's label. The host puts its status icon and text in one element; Papyros lays out the children in a row and colours them like the other tabs.
  • host-tab-panel: the content, rendered and styled by the host (it stays in the host's light DOM, so Dodona's own CSS applies). Papyros puts it in a scrollable panel below the tabs.
<p-code-runner>
  <span slot="host-tab"><i class="mdi mdi-close-circle-outline" aria-hidden="true"></i>Test #1</span>
  <div slot="host-tab-panel"><!-- the failing test, rendered by the host --></div>
</p-code-runner>

The tab appears while the host-tab slot has an element, after the add-file button and a divider. It is part of the file tablist: arrow keys, Home and End move between all tabs, Enter or Space opens one, aria-selected and the roving tabindex work as for the file tabs. The add-file button sits between the file tabs and the host tab, and a tablist may only contain tabs, so the tablist claims the host tab with aria-owns. The panel is named after the tab's text, and the name follows when the host changes that text or replaces the label element.

HOST_TAB and CODE_TAB are exported, so a host can open the tab with papyros.io.activeEditorTab = HOST_TAB. Both are symbols, so a file named code or host-tab opens as a file; every string in activeEditorTab is a file name. Starting a run selects the code again, as it already did for files, unless the run asks for another tab: papyros.runner.start(mode, { editorTab }) takes an optional RunOptions object (exported), and editorTab is the tab to select instead of the code. A host that debugs a failing test calls start(RunMode.Debug, { editorTab: HOST_TAB }). Setting activeEditorTab after start() is not reliable, because start() selects the tab before its first await and its promise only settles when the run ends. Removing the host-tab element while its tab is open selects the code. While activeEditorTab is HOST_TAB and the slot is still empty, the code panel and the Code tab show, and the host tab opens once its label arrives. The footer of the code pane keeps showing the run state while the host tab is open; the sentence in the render was a placeholder. The README documents the API.

Compact p-input. In One at a time, the text field and the Enter button are 40px high. The field was 56px (Material's default spacing) and the button stretched along with it. The padding around them is smaller and the button gets a return-key icon, as in the render. The placeholder is shorter ("Type input and press Enter", "Typ invoer en druk op Enter"), and p-input is a size container: below 25rem the Enter button turns into an icon button with an aria-label and a tooltip, so the placeholder fits at 358px in both languages. The title and the tabs did not change.

Screenshots

The column screenshots come from a test page that sets no Material spacing overrides (Dodona does not set them either). The "Copy to hand-in" button in the buttons slot is a 32px md-text-button that switches to an icon button through the container query above.

600px, before and after: Run and Debug move from below the editor into the tab bar, and the input row goes from 56px to 40px.

Before After
Before: Run and Debug below the editor, a 56px input row After: Run and Debug in the tab bar, a 40px input row

Debugging with a host tab "Test #1" open.

Light Dark
Debugging with the host tab open, light Debugging with the host tab open, dark

358px: icon buttons in one row with the tabs, and the Enter button as an icon; below that, debugging with the host tab open.

Light Dark
358px light: icon buttons next to the tabs 358px dark: icon buttons next to the tabs
358px light while debugging with the host tab 358px dark while debugging with the host tab

The demo header wraps its picker groups.

800px 390px
Demo header at 800px Demo header at 390px

How to test

  1. yarn build:sw, yarn setup, then yarn start.
  2. Run and debug a program: the buttons are in the tab bar, and while debugging only Stop debugging is there. Make the window narrow: when the tabs and the labelled buttons no longer fit on one row, and always below a code pane of 30rem, the buttons become icon buttons with tooltips. Add a wide button to the buttons slot from the console: it keeps its label until even the icon buttons leave no room, and then p-code-runner gets compact-slotted.
  3. Add a host tab from the browser console:
    const runner = document.querySelector("p-app").shadowRoot.querySelector("p-code-runner");
    runner.insertAdjacentHTML("beforeend", '<span slot="host-tab">✗ Test #1</span><div slot="host-tab-panel">The failing test</div>');
    Check that the tab appears after a divider, that the arrow keys reach it from the Code tab, and that it shows the panel. Remove the span with runner.querySelector('[slot=host-tab]').remove() while it is open: the Code tab opens. A file named host-tab or code opens in the file viewer.
  4. In the input panel, One at a time: the field and the Enter button are 40px high; in a narrow window the Enter button is an icon and the placeholder fits.

yarn test, yarn lint and yarn typecheck pass. New tests cover the host tab (absent, selected, arrow keys, removed while open, selected before its label arrives, label changes, file names that match the built-in tabs), the tab-bar height inside and outside p-code-runner, a debug run started with { editorTab: HOST_TAB } keeping the host tab, the hidden slot while debugging, the icon buttons in a narrow code pane, labelled buttons while they fit, icon buttons when a wide slotted button would wrap the bar, labelled buttons on their own row when icons would not fit either, compact-slotted only after the run buttons and its removal when the room comes back, a distinct icon per run mode, the 40px input row and the icon Enter button in a narrow input panel.

bmesuere and others added 3 commits October 11, 2026 16:39
Run, Debug and Stop now sit in the row of the file tabs as 32px buttons,
and the buttons slot follows them after a divider. While debugging the row
holds only Stop debugging. The demo app moves its example picker to the
header, since a select does not fit in a 44px toolbar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
p-code-runner takes a host-tab slot for the label of a tab that is not a
file and a host-tab-panel slot for its content. The tab follows a divider
after the add-file button and uses the same roving tabindex and arrow keys
as the file tabs. HOST_TAB and CODE_TAB are exported so the host can select
a tab from code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The outlined text field was 56px and the Enter button stretched along with
it. Both are 40px now, with less padding around them, and the button gets
a return-key icon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@bmesuere bmesuere left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Found three reproducible issues: a filename collision, a stale accessible panel name, and header overflow. Details are attached inline. The existing targeted component and debugger state tests pass (19 tests). TypeScript and lint checks on the changed source files pass. Separate reproduction tests fail for each finding; the filename and header checks pass on the base commit.

Comment thread src/frontend/components/CodeRunner.ts Resolved
Comment thread src/frontend/components/CodeRunner.ts Resolved
Comment thread src/frontend/components/app/App.ts Resolved
bmesuere and others added 7 commits October 11, 2026 17:05
CODE_TAB and HOST_TAB were the strings "code" and "host-tab", so a file
with one of those names opened the code editor or the host panel instead
of the file viewer. They are symbols now, and every string in
activeEditorTab is a file name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The panel name was read only when the host tab appeared, so replacing the
label element or changing its text left the old name on the open panel.
The label is now read on every slotchange and whenever the label's text
changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the example picker in the header, the pickers needed 859px and the
header overflowed narrower windows. The header and its picker groups now
wrap onto more rows instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
p-code-runner is now a size container named papyros-code-runner. Below
30rem the run buttons become 32px icon buttons with an aria-label and a
tooltip, and the button row no longer wraps, so the bar takes at most two
rows and the divider never ends one. Hosts can query the same container
to compact their slotted buttons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At 358px the placeholder of One at a time was cut off. The text is shorter
now ("Type input and press Enter", "Typ invoer en druk op Enter"), and
below 25rem p-input shows the Enter button as an icon button with an
aria-label and a tooltip, so the field gets the room of the label.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A slotted button taller than 32px made the button row taller than the
44px tab bar, so the two hairlines no longer met. The tab bar now grows
with the row. The README says slotted buttons should be 32px high.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Runner.start selects the code tab before its first await and only settles
when the run ends, so a host could not open its tab for the run in a
reliable way. start(mode, { editorTab }) now selects the given tab
instead; without the option it still selects the code. RunOptions is
exported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@TomNaessens TomNaessens left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Three notes from a review requested by Tom; none of them blocks this PR.

Comment thread src/frontend/state/Runner.ts Resolved
Comment thread src/frontend/components/CodeRunner.ts Resolved
Comment thread src/frontend/components/EditorTabs.ts Resolved

@TomNaessens TomNaessens left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Approving at Tom's request. The three inline notes don't block this; the overlap with the playground stack is written up on #1168, #1169, #1170 and #1171.

bmesuere and others added 2 commits October 12, 2026 15:59
Selecting HOST_TAB before the host fills the host-tab slot rendered an
empty panel with no selected tab, and no slotchange came to fall back.
The code runner and the tab bar now show the code tab in that case
without changing activeEditorTab, so the host tab opens once its label
arrives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The min-height that keeps the tab bar level with the run buttons applied
to every p-editor-tabs. p-editor-tabs is 36px again by default, and
p-code-runner gives its own tab bar the 44px minimum height.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bmesuere
bmesuere marked this pull request as ready for review October 12, 2026 15:02
@bmesuere
bmesuere requested a review from thvmulle as a code owner October 12, 2026 15:02
Copilot AI balanced review requested due to automatic review settings October 12, 2026 15:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

bmesuere and others added 5 commits October 12, 2026 17:45
Run doctests used the play icon of Run, so the two compact buttons in a
narrow code runner looked the same. It now uses mdi-playlist-check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It pairs with bug-play of Debug: a glyph for what runs, with a small
play triangle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The buttons after the first got ref(undefined). lit-html 3.3.2 throws on
that when the button is removed (Cannot set properties of undefined),
which happened on every Run in Dodona, whose lockfile has 3.3.2. 3.3.3
ignores an undefined ref, so the tests here did not see it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The run buttons became icon buttons at a fixed 30rem. A host whose slotted
button is wide (Dodona's Finalize and submit) then got a bar of two rows
between 30rem and the width everything needs, or had to compact its own
button first. p-code-runner now measures the tabs and both forms of the
buttons and shows icon buttons whenever that keeps the bar on one row.
Below 30rem they stay icons, and when even the icons do not fit, the
labelled buttons wrap to their own row as before.

The form that is not shown is laid out invisibly instead of with
display: none, so both can be measured.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A host that compacted its slotted buttons with a container query did so
at a fixed width, often while there was still room for their labels.
p-code-runner now compacts the run buttons first and, when the row still
does not fit, sets a compact-slotted attribute on itself for the host to
compact its own buttons. It keeps the width of the slotted buttons with
and without the attribute, so it does not flip back and forth.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bmesuere
bmesuere merged commit 055ec3f into main Oct 13, 2026
6 checks passed
@bmesuere
bmesuere deleted the enh/compact-code-runner branch October 13, 2026 10:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants