Sitelet https://github.com/devframes/devframe/issues/287
Skip to content

Proposal: inline dock mode for persistent side-panel workflows #287

Description

@dvcolomban

Context

The current embedded dock works well when a developer opens a tool briefly and then returns to the application. Float and edge modes both sit over the page, though, which is less comfortable when the DevTools panel is part of the workflow rather than an occasional interruption.

One example is a developer working with a persistent reference or feedback panel:

  • Keep API responses, schema documentation, component props, accessibility results, or other reference data open beside the application.
  • Interact with the application while watching that panel update.
  • Edit the source and let HMR update both the application and the DevTools view.
  • Compare the live result against the reference data without repeatedly reopening or moving an overlay.

This is similar to working with browser DevTools docked to the side. The application gets a smaller layout area, but the panel no longer covers the thing being inspected. Clicking around the page also does not dismiss it.

I would like to explore whether @devframes/hub-ui should support this as a third dock mode:

type DockMode = 'float' | 'edge' | 'inline'

Proposal

The host would opt in by providing a mount target as part of its own layout:

<main id="app"></main>
<aside id="devframes-root"></aside>
createUi({
  dockPreferences: {
    defaultMode: 'inline',
  },
})

createUi() could default the selector to #devframes-root, with an optional inlineTarget override.

A viewer adapter could provide a more natural default for its own users. For example, Vite DevTools could default to #devtools-root and expose the same override through DevTools():

DevTools({
  inlineTarget: '#custom-devtools-root',
  dockPreferences: {
    defaultMode: 'inline',
  },
})

Providing a target would make Inline available. It would not select the mode automatically, and defaultMode would remain a first-use seed rather than overriding an existing browser preference.

The host would own the surrounding grid or flex layout. DevTools would only publish state on the target:

<aside
  data-devframes-state="open"
  data-devframes-position="right"
/>

The proposed states are:

  • open: a panel is selected.
  • collapsed: the full dock rail remains visible, with no panel selected.
  • inactive: DevTools is floating, edge-mounted, hidden, unauthorized, or open in a popup.

For resizing, DevTools could reuse its existing persisted width and height and expose them as CSS custom properties:

--devframes-inline-width: 42vw;
--devframes-inline-height: 38vh;
--devframes-inline-rail-size: 40px;

The host decides how to consume or clamp those values. DevTools would not change body padding, application dimensions, or host layout styles directly.

Expected behavior

Inline would remain user-switchable alongside Float and Edge.

When active:

  • A fresh tab starts with the complete dock rail and no selected panel.
  • Selecting a dock opens its panel.
  • Selecting the active dock again collapses back to the rail.
  • Escape, Close Panel, and the other existing explicit close actions also collapse it.
  • Clicking elsewhere on the application does not close it.
  • Idle timeout and edge-toolbar auto-collapse do not apply.
  • Mode and geometry keep using the existing local-storage state.
  • Open panel, selected dock, and route keep using the existing per-tab session state.
  • Left, right, top, and bottom positions remain available. DevTools orients its rail and panel, then publishes the position for host CSS to arrange the page.

Hide DevTools and popup mode would make the target inactive rather than leaving the rail behind.

Target resolution and fallback

The configured selector should resolve to exactly one element.

It would be useful for the resolver to search the document and reachable open shadow roots, since application shells are often web components. Closed shadow roots and iframe documents would remain outside the selector contract.

Framework rendering and HMR can add or replace the target after the bootstrap starts. A small observer could retry while the target is missing and detect removal of the active target, without continuously rescanning the page once the target is stable.

If Inline is selected but no unique target can be found:

  • Log one browser-side configuration error.
  • Temporarily render Edge mode.
  • Keep the saved preference as Inline.
  • Hide Inline from mode controls while it is unavailable.
  • Restore it if the target appears later.

Possible implementation direction

I do not think moving the complete existing custom element into the target is safe.

The embedded element also owns command palettes, confirmations, toasts, menus, and tooltips. A host target may create a containing or stacking context through transform, contain, overflow, or z-index. That could trap global overlays, which is related to the positioning problem previously discussed in #205.

A safer shape would use two custom elements backed by one RPC connection and dock context:

document.body
└── embedded controller
    ├── float/edge shell when applicable
    └── command palette, dialogs, toasts, menus, tooltips

inline target
└── inline rail and selected panel

The inline shell could adapt the existing edge shell rather than introduce a second dock implementation. It would reuse the current entries, views, position controls, resizer, geometry, and persistence, with small branches for target-relative layout and manual collapse behavior.

The Vite adapter would only need to forward inlineTarget and supply its #devtools-root default. The actual behavior would stay framework-neutral in hub-ui.

Why I am opening an issue first

This looks useful, but it is larger than adding another mode branch.

The main challenges seem to be:

  • Keeping one RPC/client context while rendering the shell and global overlays in different roots.
  • Avoiding duplicate or short-lived iframe/view ownership during mode changes.
  • Handling targets inside open shadow roots and targets replaced by framework rendering.
  • Defining a host CSS contract that works for four positions and drag resizing.
  • Adding a public mode union member that downstream exhaustive switches may need to handle.

My rough estimate is a medium-sized feature: perhaps 350–600 lines of production code across devframe and the Vite forwarding layer, followed by a similar or larger amount of tests, fixture code, and documentation.

Questions for maintainers

The main question is whether this workflow feels common and useful enough to justify supporting it in @devframes/hub-ui.

Does Inline belong in the reference UI, or would you prefer hosts with this requirement to build a custom viewer against @devframes/hub/client?

If it belongs here:

  • Does the target-selector API seem like the right boundary?
  • Is supporting all four positions and the existing resizer worthwhile in the first version?
  • Is open-shadow-root lookup appropriate, or should the first version require a light-DOM target?
  • Would you prefer a smaller first pass before committing to the complete host layout contract?

I would be happy to work on an implementation if this direction feels worthwhile, but I would rather confirm that it fits the project before adding another supported presentation mode.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions