Repository navigation
feat(shell): render the web-app chrome on the web host (0.2) - #11
Merged
Merged
Conversation
API.md gains the web host (guide §5.2, web column): primary action with label collapse, legal line, File and Appearance groups, problems popover, toasts, empty-state card, drop zone and the web app's mode (head script and runtime choice). Exact signatures land as stubs in exclusive directories for three implementers: src/dom/web and src/core/web-chrome (web-chrome), src/dom/feedback and src/core/feedback (feedback), src/testing/web-cases.ts plus demo/web.* (kit-demo). mountShell wires every stub (web host only; toasts and the popover also embedded), the menu renders File, Model, Export, Appearance, Help, the fit keeps 92px for a raised zoom row, and Viewport.newDocument() lets every new web document open like the first. The IDE host is unchanged: VsCodeShellOptions does not accept the web options, and the 0.1 kit and e2e suite pass as before. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The DOM-free decisions of the web column (guide §5.7, §5.10, §5.15, §9): - decideTopRow tries the primary action with its label, then icon only, then moves the palette to the second row, where the label comes back when it keeps 16px to the menu trigger; - zoomRowRaised raises the zoom panel above the legal line where they would keep less than 8px (a shell rule the guide does not settle yet); - dragVerdict checks an open dialog, then several files, then the MIME types (only items of kind "file"; an empty type never forbids); - acceptsFile matches extensions case-insensitively and MIME types exactly or as type/*; a list without a usable entry accepts any file. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- webModeScript and webHeadMarkup write the guide's §13.3 head for the web app: a light-only modeler gets one theme-color and the contrast line only, a dark-capable one the color-scheme meta, both theme-colors and the whole script. The script is ES5, reads storage inside try/catch and JSON-escapes every literal (with <, U+2028 and U+2029), so no value ends the <script> element. - parseAppearanceChoice and documentMode give the mode for a choice. - toastLifetime, toastToEvict, toastStackWidth and toastStackBottom hold the stack's rules (§8): 2.8s, 5s for warnings and errors, never with an action, at most three, 8px above a bottom-row island the centred column would touch. - problemSummary counts the problems list's items; the new message problemSeverity names a row's severity for screen readers. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…drop zone
The web column's islands (guide §5.7, §5.10, §8, §9, §2):
- primary action: a blue button at the top right whose label collapses
before the palette moves; collapsed it is 40 × 40 with the chrome
tooltip and an unchanged aria-label; aria-disabled stays focusable;
- legal line: copyright, Impressum and Datenschutz (lang="de") with the
docs-site kit's URLs, then the modeler's links; up to 440px it scrolls
sideways with the focus ring inside;
- File and Appearance groups: New diagram, Open…, Show example,
Import… with the shared terms and icons, and the radio group System,
Light, Dark for a dark-capable modeler only;
- empty-state card: Komet, title, help, New diagram before Show example
and the drop hint; either button leaves the card first;
- drop zone: page-wide file drags only, 12px inset, over and forbidden
states with the chip, accepted files to onDrop, the rest to onReject
or a danger toast per file.
A dialog menu entry now shows its shortcut chip and aria-keyshortcuts
like an action ("Open…" as an in-page dialog keeps its ⌘O), and no
web button lifts on hover under reduced motion.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- toasts (guide §8, §9): role by status, a 4px bar and a dot in the
status colour, an optional ghost action and Close; paused while
hovered or focused, at most three, a repeat restarts the shown toast
with the newer action. In the web host the stack is a manual popover
shown again with every toast, so it stays above a showModal() dialog;
embedded it stays inside the root.
- problems popover (§5.10): the pill opens a non-modal dialog titled
"Problems" above it; rows with an element are buttons that close it,
focus the canvas and call onSelectItem; the icon names the severity
for screen readers; Escape and Tab return to the pill.
- connectWebAppearance (§13.3): the Appearance choice in localStorage
("miragon.darstellung"), data-cd-theme, color-scheme and both
theme-color metas in one task with one frame without transitions,
storage events of other tabs, and prefers-contrast mirrored on <html>.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The toast stack's bottom is recomputed when the inspector opens or closes: up to 760px its bottom sheet hides zoom panel and pill. - The top row decides again when the primary action resizes, so a label measured before Geist loaded does not keep a wrong decision. - A focus the start screen hides moves to its New diagram button; one in the problems list moves to the canvas when the last problem goes, instead of falling to <body>. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The 0.1 cases learn the web host and five cases join them after cleanup; each runs in every host and asserts outside its host that nothing of it shows. - geometry: primary action (40 wide while collapsed, 16px to the palette), legal line, the raised zoom row with its tooltip and fit, the popover above the pill, the toast stack at toastStackBottom, the centred start-screen card and the drop zone's inset; - aria: the primary action in the tab order, the legal nav with lang="de" and rel="noopener noreferrer", File first and Appearance between Export and Help, the pill as the popover's trigger; - appearance: web pages under emulateMedia, light-only never dark, the dark-capable choice stored, applied and removed in one frame; - webChrome, emptyState, toasts, problemsList and dropZone with the new show hooks emptyState and toast and the harness's drop fixtures (DropFixtures.multiple for a modeler that takes several files). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
demo/web.html mounts every web-host option the way a Miragon web app passes them, with the head markup of webHeadMarkup through the Vite config and window.__webDemo for the harness. The e2e suite runs the kit for the web host (light-only and dark-capable) against it, adds eight saboteurs for the new cases, a style spec for the web chrome's colours, radii and states, and a spec for the demo's own handlers. The 0.1 demo passes its theme colours in the web host, which a dark-capable modeler now needs. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
README: a complete web-app example (head markup, mountShell with the web column, new documents, the start screen), a web harness for the kit and the web demo. API.md: dialog entries with shortcut chips, the problemSeverity message, the measured primary label, the legal line's narrow scroll, the in-memory appearance choice, the popover's keys and focus, repeated toasts and cards, the start screen's focus, accept normalisation and the drop after a type verdict, DropFixtures.multiple, and a shorter out-of-scope list. Implementation notes: a Web app host section with the mechanics behind each island. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A web app binds Cmd/Ctrl+O and Cmd/Ctrl+S itself and shows them as
chips on its File entries. ./core labelled only the chrome keys, so every
app wrote the platform logic again ("⌘O" against "Ctrl+O", "Strg+O" in
German). commandKeyLabel(key, platform, keyNames) gives the chip, and
ariaKeyShortcutsFromLabel reads it back as aria-keyshortcuts.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…dest card Screen readers: each toast was its own role="status" or role="alert", inserted with its text into a stack that is display: none and a closed popover while empty, and raise() hid and re-showed the stack for every toast. A live region created together with its content is often not announced (NVDA with Chrome, VoiceOver with Safari), a repeated toast was never announced again, and re-showing rebuilt every shown toast in the accessibility tree. Two live regions now live in the root from mount on (.cd-shell-live: role="status" polite, role="alert"); a toast's text arrives there 100ms after the region was emptied, so a repeat speaks again, and leaves it when the toast closes. The cards carry no live role. The stack is re-shown only while a modal dialog is open. Each Close button is described by its toast's text, so three of them are told apart. Bottom row: toastStackBottom tested the whole 480px stack against the bottom row, so at 1280px with a GitHub link (legal line ends 7.5px before the stack) every toast stood at 40 instead of 12, although "Link copied" is 146px wide in the middle. ToastStackInput.cardWidth takes the widest card's measured width; the shell passes it and recomputes whenever a toast comes or goes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Problems list: rows without an element were plain text that never took the focus, and the popover consumed the arrow keys, so a keyboard could not read past the visible rows of a list of line findings (WCAG 2.1.1). Every row now takes the focus (text rows tabindex="-1"), ↑/↓/Home/End move through all rows and scroll the focused one into the list (only the list scrolls, never the root), and the popover itself shows an inset focus ring when it holds the focus. - Start screen keys: Cmd/Ctrl + `+`/`−`/`0` and Shift+1 were prevented and did nothing, so a low-vision user could neither zoom the page nor reset a stored zoom. With no diagram to zoom they now reach the browser (KeyContext.startScreen); the diagram-js guard returns false for them meanwhile, so diagram-js zooms nothing either. - Start screen focus: closing a focused toast fell back to the inert canvas and the hidden menu trigger, both focus() calls failed and the focus landed on <body>. It now goes to "New diagram", and the fallback checks that a target really took the focus. - Start screen Open: the menu is hidden and a drop needs a pointer. With file.open the hint ends in a text link "Open a file…" with the shortcut chip and aria-keyshortcuts (messages.openFileLink); it is part of the hint, not a third action, and leaves the card up until the file loaded. - The web host's canvas slot is the page's main landmark. - High contrast: the start-screen card keeps --cd-ui-linie like every other island (the role already turns it into kontur, guide §5.3). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…it off Up to 440px the legal line scrolled sideways without a visible scrollbar: at 320 CSS px (1280px at 400 % zoom, WCAG 1.4.10) Datenschutz was cut mid-word and GitHub could not be seen, and Datenschutz has to be reachable for legal reasons as well. The line is now at most 100cqw − 24px wide and wraps between its entries, each separator kept with the link after it (.cd-shell-legal-item), growing upwards from bottom 12. The shell measures its height in whole lines (legalLineHeight, LEGAL_LINE_HEIGHT) into --modeler-shell-legal-height on the root, and the pill, the problems popover, the legend, the raised zoom panel and the fit's bottom (FitLayout.legalHeight, 112 on two lines) stand on it. Every legal link is described as opening a new tab (messages.opensInNewTab). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nce kit - toasts: a role="status" and a role="alert" live region in the root before the first toast, the toast's text arriving in the one of its status, no live role on the card. Cards are found by [data-status]. - geometry: toasts at toastStackBottom with the widest card (ChromeMeasurement.toastCardWidth); the legal line 20 high per line and at most 100cqw − 24px wide, pill, legend, raised zoom panel, tooltip row and fit standing on its measured lines. - webChrome: at 320px every legal link lies whole inside the line. - problemsList: Enter on the pill, ↓ to the last row, Home and End reach every row, text rows included, each scrolled into the list (PopoverFacts.focusedRow). absent: ["problemSelection"] declares a modeler whose problems never point at an element, so no row selects and nothing is skipped. - emptyState: the chrome keys reach the browser unprevented without zooming the diagram; with show.toast, closing a focused toast puts the focus on the card; a text link in the hint is no third action. Four saboteurs prove the new rules (live regions that come with the toast, a text row that cannot take the focus, a start screen that swallows the zoom keys, a legal line that scrolls), and the style spec covers the hint's Open link. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
API.md: the toasts' live regions, Close descriptions and the widest-card bottom row; the problems list's focusable text rows; the start screen's keys, focus and Open link; the wrapping legal line with its measured height, FitLayout.legalHeight, LEGAL_LINE_HEIGHT and legalLineHeight; commandKeyLabel; the main landmark; the new messages; the kit's new checks, ChromeMeasurement.toastCardWidth and absent "problemSelection"; that a renderer's export takes the canvas container's own <svg> and strips the live canvas's ARIA. The Event Storming usage now follows the real app (copy inside the click, platform-aware chips, the counted warning toast, the withdrawn undo offer). README: commandKeyLabel in the web example, the Vite 8 setting that keeps the licence comment. Implementation notes: why each of these changed, and that "dialog" in §5.8 is read as a modal dialog. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Oct 19, 2026
Merged
dominikhorn93
pushed a commit
that referenced
this pull request
Oct 19, 2026
🤖 I have created a release *beep* *boop* --- <details><summary>modeler-shell: 0.2.0</summary> ## [0.2.0](modeler-shell-v0.1.0...modeler-shell-v0.2.0) (2026-10-19) ### Features * **shell:** render the web-app chrome on the web host (0.2) ([#11](#11)) ([e08d63a](e08d63a)) </details> --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: miragon-release-please[bot] <292359359+miragon-release-please[bot]@users.noreply.github.com>
12 of 26 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the "web-app chrome" part of #4. @miragon/modeler-shell 0.1 covered the chrome for IDE hosts; 0.2 adds the web column of guide §5.2. A Miragon web app now gets its whole chrome from
mountShell({ host: "web" }), so the Event Storming web app looks and behaves like its VS Code webview.What 0.2 adds
decideTopRow).lang="de"(the corporate-identity URLs), then the modeler's own links.commandKeyLabelgives the chips for an app's own Cmd/Ctrl keys.webHeadMarkup/webModeScript(§13.3), plus the runtimeshell.appearance.shell.toast().Review round (in this PR)
Six commits on top of the first implementation fix what three reviews (spec, Event Storming integration, accessibility) found:
<body>.Testing
npm run lint,npm test(api 78, shell 1182),npm run build,publint.playwright test: 136/136. The kit runs on the ide, web and embedded hosts plus the dark-capable variants, and all saboteurs pass.Notes
featcommits make 0.2.0. The Event Storming pilot (Miragon/event-storming-modeler,feat/webapp-shell) runs on a local pack of this branch until 0.2.0 is published.🤖 Generated with Claude Code