feat(dashboard): right-dock views render their mobile layout in the narrow dock
Container queries on the dock-body container so embedded views use their single-column mobile layout in the narrow dock: Git Manager (full mobile layout incl. labeled tabs + mobile remotes view), Activity Log, Dev Server (config/logs/preview stack full-width and scroll), Secrets, Pull Requests. Wide pop-out keeps the desktop layout. 80 view tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -720,3 +720,111 @@ FNXC:DevServer 2026-06-22-01:00:
|
||||
font-size: 0.625rem;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:RightDockEmbedded 2026-06-22-00:00:
|
||||
When DevServerView is hosted in the narrow right dock, the dock body's `right-dock-body` query container drives this
|
||||
block. The viewport is desktop, so the desktop min-width:769px two-column grid above also fires and would overflow
|
||||
the narrow dock — here we override it back to a single scrollable column and mirror the phone-width max-width:768px
|
||||
stacking: the config and preview panels stack full-width, the section sheds its max-width cap, preview header/url/
|
||||
actions wrap, candidate rows wrap, and logs/preview keep a bounded min-height. The whole view already scrolls
|
||||
vertically (.dev-server-view overflow-y:auto), so each panel just needs to be full-width and not overflow.
|
||||
*/
|
||||
@container right-dock-body (max-width: 768px) {
|
||||
.dev-server-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 var(--space-md) var(--space-md);
|
||||
}
|
||||
|
||||
.dev-server-view > .view-header {
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.dev-server-header-title {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.dev-server-header-actions {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dev-server-config {
|
||||
grid-column: auto;
|
||||
grid-row: auto;
|
||||
max-height: min(48vh, calc(var(--space-2xl) * 13));
|
||||
}
|
||||
|
||||
.dev-server-content {
|
||||
grid-column: auto;
|
||||
grid-row: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.devserver-preview-panel {
|
||||
grid-column: auto;
|
||||
grid-row: auto;
|
||||
}
|
||||
|
||||
.dev-server-section {
|
||||
padding: var(--space-md);
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.devserver-preview-header {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.devserver-preview-url-badge {
|
||||
order: 2;
|
||||
flex: 1 1 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.devserver-preview-actions {
|
||||
order: 3;
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.dev-server-preview-override {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.dev-server-candidate,
|
||||
.dev-server-selected {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.dev-server-candidate-command {
|
||||
flex: 1 1 100%;
|
||||
max-width: 100%;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.dev-server-candidate-source {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.dev-server-candidates {
|
||||
max-height: min(32vh, calc(var(--space-2xl) * 7));
|
||||
}
|
||||
|
||||
.dev-server-logs,
|
||||
.devserver-preview-container,
|
||||
.devserver-preview-iframe {
|
||||
min-height: calc(var(--space-2xl) * 4 + var(--space-md));
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.devserver-preview-blocked-panel,
|
||||
.devserver-preview-error-panel,
|
||||
.devserver-preview-external-only {
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,3 +256,46 @@ The view now renders the shared ViewHeader at the top, which supplies the --spac
|
||||
color: var(--color-error);
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:RightDockEmbedded 2026-06-22-00:00:
|
||||
PullRequestView has no @media (max-width:768px) block, so there is nothing to mirror — but in the narrow right dock
|
||||
(~280-420px, desktop viewport) the `margin-left:auto` push-offs (identity state badge, auto-merge toggle, thread id)
|
||||
and inline action rows can overflow horizontally. Under the dock body's `right-dock-body` query container, drop the
|
||||
auto-margins so those items flow inline-and-wrap, let the identity/action/summary rows wrap, and keep the thread
|
||||
reply indent modest so the single scrollable column never overflows sideways. View behavior is unchanged.
|
||||
*/
|
||||
@container right-dock-body (max-width: 768px) {
|
||||
.pr-view {
|
||||
padding: 0 var(--space-md) var(--space-md);
|
||||
}
|
||||
|
||||
.pr-identity-state {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.pr-action-bar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.pr-action {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pr-automerge-toggle {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.pr-thread-head {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.pr-thread-id {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.pr-thread-reply {
|
||||
margin-left: var(--space-sm);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1213,6 +1213,79 @@ The embedded root is a plain flow box that fills the dock; the inner panel sheds
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
resize: none;
|
||||
/*
|
||||
FNXC:RightDockEmbedded 2026-06-22-00:00:
|
||||
The dock is narrow (~280-420px) while the viewport stays desktop, so the view's @media (max-width:768px) mobile
|
||||
rules never fire. Make the embedded panel an inline-size query container so the dock width — not the viewport —
|
||||
drives the mobile single-column layout below. See the @container activity-log-embedded block below.
|
||||
*/
|
||||
container-type: inline-size;
|
||||
container-name: activity-log-embedded;
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:RightDockEmbedded 2026-06-22-00:00:
|
||||
Mirror the phone-width (@media max-width:768px) activity-log layout-stacking rules for the narrow dock, scoped to the
|
||||
embedded variant. Header wraps (title + close on top row, actions/filters stack full-width), filters/selects go 100%,
|
||||
the active-filters bar wraps, and entry headers/details/text wrap instead of overflowing horizontally. Only layout
|
||||
stacking is mirrored; behavior and the real @media rules are untouched.
|
||||
*/
|
||||
@container activity-log-embedded (max-width: 560px) {
|
||||
.activity-log-modal--embedded .activity-log-header {
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-sm);
|
||||
padding: var(--space-md) var(--space-lg);
|
||||
}
|
||||
|
||||
.activity-log-modal--embedded .activity-log-title {
|
||||
flex: 1 1 auto;
|
||||
order: 0;
|
||||
}
|
||||
|
||||
.activity-log-modal--embedded .activity-log-actions {
|
||||
flex: 1 1 100%;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-xs);
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.activity-log-modal--embedded .activity-log-filter,
|
||||
.activity-log-modal--embedded .activity-log-filter--project {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.activity-log-modal--embedded .activity-log-filter-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.activity-log-modal--embedded .activity-log-active-filters {
|
||||
flex-wrap: wrap;
|
||||
padding: var(--space-sm) var(--space-lg);
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
.activity-log-modal--embedded .activity-log-clear-filters {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.activity-log-modal--embedded .activity-log-content {
|
||||
padding: var(--space-md) var(--space-lg);
|
||||
}
|
||||
|
||||
.activity-log-modal--embedded .activity-log-entry-header {
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
.activity-log-modal--embedded .activity-log-entry-details {
|
||||
flex-wrap: wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.activity-log-modal--embedded .activity-log-entry-text {
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1976,57 +2049,101 @@ The embedded host fills its right-dock container; the inner shell drops overlay-
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:GitManager 2026-06-22-00:25:
|
||||
The embedded Git Manager adapts to its container width, not the viewport, so the SAME embedded render works in both the narrow right dock and the wide pop-out (expand) modal:
|
||||
- Wide container (expand modal): inherits the default desktop layout — vertical section sidebar + content (two-pane, like before).
|
||||
- Narrow container (dock, < 560px): section tabs become a horizontal strip above a full-width content pane, and each tab is compact (min-width:0, tight padding, inline icon+label) so MORE tabs fit in the strip at once. The content collapses to a single column.
|
||||
FNXC:GitManager 2026-06-22-17:30:
|
||||
The embedded Git Manager adapts to its CONTAINER width, not the viewport, so the SAME embedded render works in both the narrow right dock and the wide pop-out (expand) modal:
|
||||
- Wide container (expand modal, > 560px): inherits the default desktop layout — vertical section sidebar + content (two-pane, like before). Those default rules live OUTSIDE this container query and are untouched.
|
||||
- Narrow container (dock, <= 560px): MIRRORS the phone-width (@media max-width:768px) gm layout exactly. Section tabs become a horizontal strip with ICON + TEXT LABEL per tab (the mobile .gm-nav-item: column, icon over label, comfortable padding), and every section (status grid, create form, branches, stashes, changes/files split, and the REMOTES view) collapses to its mobile single-column form. The remotes view is CSS-only (no JS width gating in GitManagerModal.tsx), so mirroring the @media remotes rules here gives the dock the mobile remotes selector strip + stacked detail.
|
||||
The previous bespoke rules here hid the tab labels (icon-only) and used a cramped strip; the user wants labeled tabs, more spacing, and the mobile remotes layout, so we now mirror the @media gm INTERNAL rules verbatim under the .gm-modal--embedded prefix. Only the viewport-takeover (.modal.gm-modal 100vw/100dvh) rules are NOT mirrored — they are scoped to :not(.gm-modal--embedded) and must never apply to the embedded pane.
|
||||
*/
|
||||
@container gm-embedded (max-width: 560px) {
|
||||
.gm-modal--embedded .gm-changes-split {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-changes-lists {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-file-section {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-file-section-header {
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-file-section-header h5 {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-file-section-actions {
|
||||
flex: 1 1 100%;
|
||||
flex-wrap: wrap;
|
||||
min-width: 0;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-file-section-actions .btn {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-file-item {
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
row-gap: var(--space-xs);
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-file-checkbox,
|
||||
.gm-modal--embedded .gm-file-icon,
|
||||
.gm-modal--embedded .gm-file-badge,
|
||||
.gm-modal--embedded .gm-file-item .gm-icon-btn {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-file-name {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* ── Section tabs: mobile horizontal strip with icon + TEXT LABEL ── */
|
||||
.gm-modal--embedded .gm-layout {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:GitManager 2026-06-22-01:35:
|
||||
In the narrow dock the section tabs are ICON-ONLY (the label is visually hidden but kept for screen readers; the button title/aria-label provides a tooltip). This makes each tab a compact icon-sized square so many tabs are visible horizontally at once. The strip is a single short row.
|
||||
*/
|
||||
.gm-modal--embedded .gm-sidebar {
|
||||
flex: 0 0 auto;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
min-height: calc(var(--space-2xl) + var(--space-md));
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--border);
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
padding: calc(var(--space-xs) / 2);
|
||||
gap: calc(var(--space-xs) / 2);
|
||||
touch-action: pan-x pan-y;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overscroll-behavior-x: contain;
|
||||
padding: var(--space-xs) var(--space-sm);
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-nav-item {
|
||||
/* width:auto resets the base .gm-nav-item width:100% which otherwise made each tab span the whole strip (one wide tab at a time). */
|
||||
flex: 0 0 auto;
|
||||
width: auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0;
|
||||
padding: var(--space-xs);
|
||||
flex-direction: column;
|
||||
gap: calc(var(--space-xs) / 2);
|
||||
padding: var(--space-xs) var(--space-sm);
|
||||
border-left: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-nav-label {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
font-size: var(--font-size-xs);
|
||||
min-width: calc(var(--space-2xl) + var(--space-xl));
|
||||
min-height: calc(var(--space-xl) + var(--space-sm));
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-nav-item.active {
|
||||
@@ -2034,6 +2151,15 @@ The embedded Git Manager adapts to its container width, not the viewport, so the
|
||||
border-bottom-color: var(--todo);
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-content {
|
||||
min-height: 200px;
|
||||
padding: var(--space-md);
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-search-box input {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-status-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
@@ -2041,6 +2167,109 @@ The embedded Git Manager adapts to its container width, not the viewport, so the
|
||||
.gm-modal--embedded .gm-create-form {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-create-form input,
|
||||
.gm-modal--embedded .gm-create-form select {
|
||||
flex: 1 1 100%;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-branch-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-stash-header {
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-stash-actions {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-remote-actions {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-pull-split {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-pull-split-toggle {
|
||||
min-width: 36px;
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-pull-menu {
|
||||
left: 0;
|
||||
right: auto;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
/* ── Remotes view: mobile single-column (selector strip + stacked detail) ── */
|
||||
.gm-modal--embedded .gm-remotes-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-remote-selector {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
border-right: 1px solid var(--border);
|
||||
flex-direction: row;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
padding-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-remote-selector-header {
|
||||
flex-direction: column;
|
||||
gap: var(--space-xs);
|
||||
padding: 0 var(--space-xs) 0 0;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-remote-selector-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-xs);
|
||||
min-width: 120px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-remote-selector-name {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-remote-detail {
|
||||
min-height: 200px;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-remote-form {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-remote-form .gm-input,
|
||||
.gm-modal--embedded .gm-remote-form .gm-input-url {
|
||||
width: 100%;
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-remote-detail-url-row {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-remote-inline-actions {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.gm-modal--embedded .gm-commit-form .gm-commit-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* Main layout: sidebar + content */
|
||||
|
||||
@@ -231,3 +231,45 @@ The standalone Secrets page is mounted as a flex item inside the .project-conten
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:RightDockEmbedded 2026-06-22-00:00:
|
||||
SecretsView has no dedicated embedded variant; in the narrow right dock it renders directly under the dock body's
|
||||
`right-dock-body` query container. The viewport stays desktop so its @media (max-width:768px) rules never fire.
|
||||
Mirror the phone-width layout stacking off the DOCK width: the two-column .secrets-row grid collapses to a single
|
||||
column, the header / sync-header stack, sync copy/actions go full-width, and the action side rail becomes full-width
|
||||
so nothing overflows horizontally in the narrow dock.
|
||||
*/
|
||||
@container right-dock-body (max-width: 768px) {
|
||||
.secrets-view {
|
||||
padding-inline: var(--space-md);
|
||||
}
|
||||
|
||||
.secrets-header,
|
||||
.secrets-row,
|
||||
.secrets-sync-header {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.secrets-sync-header {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.secrets-sync-copy {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.secrets-sync-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.secrets-row-side {
|
||||
width: 100%;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user