On a phone in desktop mode the opened card starts below the header bar.

The header wraps to several rows on a phone, far taller than the 8px the
floating card docks at, so the card's top row sat among the header's rows and
was only seen by dragging. Below 801px a card without an inline top, popup
cards included, starts at --wekan-header-height + 8px; a dragged place is an
inline top and is never overridden.

Thanks to the iPhone 12 mini reporter and xet7 !
This commit is contained in:
Lauri Ojansivu committed 2026-11-04 03:03:59 +02:00
1 parent d87baa0244
commit efaf94242e
2 files changed
+45

No files matched your search

+17
View File
@@ -185,6 +185,23 @@ body.desktop-mode .card-details:not(.card-details-popup):not(.card-details-colla
bottom: 8px;
}
/* A phone in desktop mode: the header bar wraps to several rows (up to five with
the board controls open), far taller than the 8px the floating window docks
at, so the card's own top row (caret, number, menu, X) sat among the header's
rows and was only seen by dragging the card down. Below 801px the card starts
BELOW the header, at the height Utils publishes as --wekan-header-height.
Only a card WITHOUT an inline top matches (`:not([style*="top"])`), so the
place a finger dragged it to is an inline top and is kept when the finger is
lifted - this rule never competes with it. */
@media screen and (max-width: 800px) {
body.desktop-mode .card-details:not(.card-details-popup):not([style*="top"]),
/* a card opened as a popup (search result, table, Roadmap bar) docks the same way */
body.desktop-mode .card-details.card-details-popup:not([style*="top"]),
body.desktop-mode .card-details.card-details-maximized:not(.card-details-popup):not(.card-details-collapsed) {
top: calc(var(--wekan-header-height, 0px) + 8px) !important;
}
}
/* #4822: the MAXIMIZED card must be anchored to the VIEWPORT (position: fixed
with viewport insets), never laid out inside — or measured from — the
scrolled swimlane/board container. Historically the maximized pane was an
+28
View File
@@ -0,0 +1,28 @@
'use strict';
// A phone in desktop mode: the opened card starts below the (multi-row) header bar and a
// dragged place is kept. Run: node tests/cardBelowHeaderOnPhone.test.cjs
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { test } = require('node:test');
const css = fs.readFileSync(path.join(__dirname, '..', 'client/components/cards/cardDetails.css'), 'utf8');
const block = /@media screen and \(max-width: 800px\) \{\n body\.desktop-mode \.card-details:not\(\.card-details-popup\):not\(\[style\*="top"\]\),[^{]*\{([^}]*)\}/.exec(css);
test('below 801px the desktop-mode card starts at the measured header height', () => {
assert.ok(block, 'the phone rule exists');
assert.match(block[1], /top: calc\(var\(--wekan-header-height, 0px\) \+ 8px\) !important;/);
});
test('negative: a card with an inline top (dragged) never matches it, so the drag sticks', () => {
const selectors = css.slice(css.indexOf(block[0]), css.indexOf(block[1])).replace(/\/\*.*?\*\//g, '').split(',');
assert.ok(selectors.every(s => /:not\(\[style\*="top"\]\)|card-details-maximized/.test(s)));
// the variable is published by Utils, so the rule has a source
assert.match(fs.readFileSync(path.join(__dirname, '..', 'client/lib/utils.js'), 'utf8'), /--wekan-header-height/);
});
test('a card opened as a popup starts below the header too, not at the top of the window', () => {
assert.ok(block[0].includes('.card-details.card-details-popup:not([style*="top"])'));
});