* fix(#6906): tighten Kanban modal viewport regression * fix(#6906): preserve modal centering fallbacks * docs(changelog): note the #6906 Kanban modal height-cap fix --------- Co-authored-by: nesquena-hermes <nesquena+hermes@gmail.com> Co-authored-by: n <a@n>
This commit is contained in:
3 files changed
+128
-1
No files matched your search
@@ -5,6 +5,8 @@
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Kanban task and board dialogs stay usable in short windows.** On a short window (short-wide landscape, small laptop, or a phone in landscape) the Kanban create/edit-task and create-board dialogs were taller than the viewport with no height cap and no internal scroll, so the dialog overflowed both the top and bottom edges and its action buttons (Create / Save / Cancel) were unreachable. The dialogs now cap their height to the viewport (`calc(100dvh - 48px)`, with a `100vh` fallback) and scroll their content internally, and the overlay uses safe centering so the top stays reachable when the dialog is taller than the window. Normal-height dialogs are unchanged. Thanks @rodboev. (#6906)
|
||||
|
||||
- **A stale approval card no longer dead-ends with "Approval response not accepted."** On the local backend, clicking a dangerous-command approval card whose approval had already been resolved or cleared could leave the card stuck showing an error, because WebUI failed to match the resolved approval back to its producer (the agent core delivers the approval as a copy that carries a `request_id` but no `approval_id`, so the existing identity/`approval_id` matches both missed and a tokenless mirror was orphaned). WebUI now also matches on the core's per-approval `request_id`, so a resolved/stale card clears gracefully. (#4948)
|
||||
|
||||
- **Docker single-container: an explicitly configured UID/GID is no longer lost, fixing a restart loop.** The entrypoint now probes the configured state-dir bind mount before the image-owned `/workspace` when auto-detecting the container UID/GID, and persists an `explicit` marker so a deliberately supplied `WANTED_UID`/`WANTED_GID` (e.g. `1024`) survives the root→user re-entry instead of being re-detected and overwritten. This fixes the documented single-container restart loop; the default stays `1024`, the two- and three-container topologies are unaffected, and a missing legacy marker falls back safely. Thanks @jorgejiro. (#7027)
|
||||
|
||||
+4
-1
@@ -6848,7 +6848,7 @@ main.main.showing-insights > #mainInsights{display:flex;overflow-y:auto;}
|
||||
feels native to the WebUI rather than a one-off bridge UI. */
|
||||
.kanban-modal-overlay{
|
||||
position:fixed;inset:0;background:rgba(7,12,19,.62);backdrop-filter:blur(6px);
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
display:flex;align-items:center;align-items:safe center;justify-content:center;overflow-y:auto;
|
||||
z-index:1100;padding:24px;
|
||||
}
|
||||
.kanban-modal-overlay[hidden]{display:none;}
|
||||
@@ -6861,6 +6861,9 @@ main.main.showing-insights > #mainInsights{display:flex;overflow-y:auto;}
|
||||
padding:18px 18px 16px;
|
||||
color:var(--text);
|
||||
box-sizing:border-box;
|
||||
max-height:calc(100vh - 48px);
|
||||
max-height:calc(100dvh - 48px);
|
||||
overflow-y:auto;
|
||||
}
|
||||
:root:not(.dark) .kanban-modal{
|
||||
background:linear-gradient(180deg,#fff,#f5f0e8);
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
"""Regression coverage for reachable Kanban modal actions in short windows."""
|
||||
|
||||
import pytest
|
||||
|
||||
from tests._layout_helpers import assert_layout_sane, assert_no_raw_i18n_keys
|
||||
from tests._pytest_port import BASE
|
||||
|
||||
|
||||
_BROWSER_ARGS = ["--no-sandbox", "--disable-dev-shm-usage"]
|
||||
_LOCALES = ["en", "ru", "de"]
|
||||
|
||||
|
||||
def _open_modal(page, modal_id, locale):
|
||||
page.goto(BASE + "/", wait_until="domcontentloaded")
|
||||
page.wait_for_function("() => typeof S !== 'undefined' && S._bootReady === true", timeout=10000)
|
||||
page.wait_for_function(
|
||||
"() => typeof setLocale === 'function' && typeof applyLocaleToDOM === 'function'",
|
||||
timeout=10000,
|
||||
)
|
||||
page.evaluate(
|
||||
"""([lang, id]) => {
|
||||
setLocale(lang);
|
||||
if (id === 'kanbanTaskModal') openKanbanCreate();
|
||||
else openKanbanCreateBoard();
|
||||
applyLocaleToDOM();
|
||||
const modal = document.getElementById(id);
|
||||
if (!modal || modal.hidden) throw new Error(`${id} did not open`);
|
||||
}""",
|
||||
[locale, modal_id],
|
||||
)
|
||||
|
||||
|
||||
def _assert_reachable(page, modal_id, *, should_scroll, max_height_inset):
|
||||
result = page.evaluate(
|
||||
"""(id) => {
|
||||
const overlay = document.getElementById(id);
|
||||
const modal = overlay?.querySelector('.kanban-modal');
|
||||
const actions = modal?.querySelector('.kanban-modal-actions');
|
||||
if (!overlay || !modal || !actions) throw new Error('modal geometry is incomplete');
|
||||
if (modal.scrollHeight > modal.clientHeight) modal.scrollTop = modal.scrollHeight;
|
||||
const viewport = {width: innerWidth, height: innerHeight};
|
||||
const overlayBox = overlay.getBoundingClientRect();
|
||||
const modalBox = modal.getBoundingClientRect();
|
||||
const actionsBox = actions.getBoundingClientRect();
|
||||
return {
|
||||
viewport,
|
||||
overlay: overlayBox.toJSON(),
|
||||
modal: modalBox.toJSON(),
|
||||
actions: actionsBox.toJSON(),
|
||||
clientHeight: modal.clientHeight,
|
||||
scrollHeight: modal.scrollHeight,
|
||||
maxHeight: getComputedStyle(modal).maxHeight,
|
||||
maxHeightPx: parseFloat(getComputedStyle(modal).maxHeight),
|
||||
};
|
||||
}""",
|
||||
modal_id,
|
||||
)
|
||||
assert result["overlay"]["x"] == pytest.approx(0)
|
||||
assert result["overlay"]["y"] == pytest.approx(0)
|
||||
assert result["modal"]["bottom"] <= result["viewport"]["height"] + 1
|
||||
assert result["modal"]["top"] >= -1
|
||||
assert result["actions"]["bottom"] <= result["modal"]["bottom"] + 1
|
||||
assert result["actions"]["top"] >= result["modal"]["top"] - 1
|
||||
assert result["maxHeightPx"] == pytest.approx(result["viewport"]["height"] - max_height_inset, abs=1)
|
||||
if should_scroll:
|
||||
assert result["scrollHeight"] > result["clientHeight"]
|
||||
assert result["maxHeight"] != "none"
|
||||
else:
|
||||
assert result["scrollHeight"] == pytest.approx(result["clientHeight"], abs=1)
|
||||
assert_no_raw_i18n_keys(page, f"#{modal_id}")
|
||||
assert_layout_sane(page, f"#{modal_id}")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("locale", _LOCALES)
|
||||
@pytest.mark.parametrize(
|
||||
"width,height,should_scroll,max_height_inset",
|
||||
[
|
||||
(1280, 720, True, 48),
|
||||
(1440, 800, True, 48),
|
||||
(800, 450, True, 48),
|
||||
(1920, 1080, False, 48),
|
||||
(400, 800, True, 24),
|
||||
(640, 480, True, 24),
|
||||
],
|
||||
)
|
||||
def test_task_modal_actions_reachable_across_viewports(
|
||||
locale, width, height, should_scroll, max_height_inset
|
||||
):
|
||||
pw = pytest.importorskip("playwright.sync_api")
|
||||
with pw.sync_playwright() as playwright:
|
||||
browser = playwright.chromium.launch(headless=True, args=_BROWSER_ARGS)
|
||||
page = browser.new_page(viewport={"width": width, "height": height})
|
||||
try:
|
||||
_open_modal(page, "kanbanTaskModal", locale)
|
||||
_assert_reachable(
|
||||
page,
|
||||
"kanbanTaskModal",
|
||||
should_scroll=should_scroll,
|
||||
max_height_inset=max_height_inset,
|
||||
)
|
||||
finally:
|
||||
page.close()
|
||||
browser.close()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("locale", _LOCALES)
|
||||
def test_board_modal_inherits_reachable_modal_geometry(locale):
|
||||
pw = pytest.importorskip("playwright.sync_api")
|
||||
with pw.sync_playwright() as playwright:
|
||||
browser = playwright.chromium.launch(headless=True, args=_BROWSER_ARGS)
|
||||
page = browser.new_page(viewport={"width": 800, "height": 450})
|
||||
try:
|
||||
_open_modal(page, "kanbanBoardModal", locale)
|
||||
_assert_reachable(
|
||||
page,
|
||||
"kanbanBoardModal",
|
||||
should_scroll=True,
|
||||
max_height_inset=48,
|
||||
)
|
||||
finally:
|
||||
page.close()
|
||||
browser.close()
|
||||
Reference in new issue
Block a user