Touch is a panel setting, and the rail grows with it

It described the wrong object. A dashboard is a document that may hang on a
hallway tablet and in a desk browser at the same time, and only one of those
has fingers on it — so the flag moves off `DashboardDef.settings` and onto
`PanelDef` as a plain bool, ticked in the Panels dialog. `useCanvasRoot` takes
it as an argument rather than reading the document, and `/panel/{id}` is the
only surface with a panel to ask.

Dropping the message binding with it is deliberate: nothing drove it, and a
flow deciding whether a screen has fingers on it was never the point. A stored
`settings.touch` is inert rather than migrated, which `_check_settings`
skipping unknown names already guaranteed.

The rail was the other half. It had no touch behaviour at all and its 40px
buttons met neither branch of the 44/32 rule. `[data-touch] .dui-rail{-item}`
in `ui/core/core.css` spends the padding and the gap on the buttons instead,
so they reach the 44px target and the rail comes out taller at exactly the
same width — `RAIL_INSET` never moves, and the arrangement under it does not
either.

Also closes the panels-dialog icon gap: `DashboardSummary` carries the `icon`
now, so the dialog draws each assigned dashboard's rail glyph beside its
checkbox. `initials()` went from three identical copies in the looks to one in
`Dashboard/icons.ts`, so the dialog and the rail fall back the same way.
This commit is contained in:
2026-08-31 19:04:20 +02:00
parent c781c199f3
commit 1faf7f7c11
22 changed files with 308 additions and 124 deletions
+32
View File
@@ -86,6 +86,38 @@ def test_assign_and_read_back(
)
def test_touch_belongs_to_the_panel(
client: TestClient, superuser_token_headers: dict[str, str]
) -> None:
"""Whether a screen is touched is a fact about the screen, not the document.
The same dashboard may hang on a hallway tablet and on a desk browser, so
the flag rides on the panel and each one answers for itself.
"""
client.post(f"{DASHBOARDS}/shared", headers=superuser_token_headers)
_panels(
client,
superuser_token_headers,
{
"panels": [
{"id": "wall", "dashboards": ["shared"], "touch": True},
{"id": "desk", "dashboards": ["shared"]},
]
},
)
by_id = {
panel["id"]: panel
for panel in client.get(f"{PREFIX}/", headers=superuser_token_headers).json()[
"panels"
]
}
assert by_id["wall"]["touch"] is True
# Absent is pointed at, which is what a panels file written before this
# field existed comes back as.
assert by_id["desk"]["touch"] is False
def test_duplicate_panel_is_refused(
client: TestClient, superuser_token_headers: dict[str, str]
) -> None: