The dashboard is a grid a person arranges by dragging #125
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Depends on
#124 Placing a widget without a mouse, in two dimensions
Postulo/postulo
Reference
Postulo/postulo#125
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Observation
Third of three. Blocked by the two above: one decides where an arrangement is stored, the
other decides how a widget is placed by somebody not using a mouse.
What exists
Seventeen widgets are registered, seven of them in the default arrangement:
suggestions,counters,gone_quiet,upcoming_interviews,due_reminders,recent_activity,shortcuts,listingsinsight_counters,funnel,outcomes,durations,interviews_summary,sources,industries,by_month,selectivityEach declares a width of
quarter,halforfull, andcore/dashboard.htmlturns thatinto
lg:col-span-3,-6or-12over a twelve-column grid, in one flow, ordered bythe stored list. There are no positions: a widget has a width and a place in a queue.
Sourceshands every widget on a page the same computed answers, so the funnel, theresponse rate and the reply times cost one walk of the event log rather than three.
The registry is a plain
dictfilled fromAppConfig.ready— widgets are not pluginstoday, and nothing in Server settings → Plugins lists them.
What this asks for
Worth being careful about
Four or five is not a free choice, and five is the harder one. Twelve was picked
because a quarter, a half and a whole all divide into it. Four divides by halves and
quarters; five divides by nothing useful — a "half-width" widget in five columns is two
columns or three, never half, and the seventeen widgets currently declare their widths in
exactly those words. Either the widths stop being
quarter/half/fulland become spansin the new grid's own units, or the grid is four. This should be settled before anything is
built on top of it.
Tailwind compiles the classes it can see. The template writes
lg:col-span-3and itssiblings out literally, with a comment saying why: a class assembled at runtime from stored
data is a class Tailwind never compiled, and it silently does nothing. A grid whose spans
and starts come out of a person's saved arrangement needs either an exhaustive written-out
set or a safelist, decided deliberately.
A grid of four or five columns has to survive a phone. #73 is open on exactly that, and
this must not assume it is solved. The stored arrangement is the wide-screen one; what a
narrow screen does with it — collapse to one column in reading order, or keep two — is part
of this issue, and the answer determines whether a person's careful placement means
anything on the device they are most likely to check on.
Right to left. Column one is the start edge, not the left. The template lint already
fails a physical direction class, and grid placement is where that is easiest to get wrong.
Holes and overlaps. A true coordinate model admits both: two widgets in one cell, and a
gap in the middle of the page. A packed model with spans admits neither and is far simpler
to store, validate and render. Which one this is decides how much validation the save path
needs, and how a grid degrades when a widget is uninstalled and leaves a hole.
"Internal widgets plugin" is a bigger phrase than it looks. The plugin kinds today
either read something outside (
source,importer), talk to a service (notifier,store,sync), carry mail (transport) or govern a capability of Postulo itself(
feature, added in #90). A widget is none of those: it contributes a thing to a page.Making it a kind means deciding:
switch off, and whether that is an improvement or a wall;
rule is that switching a plugin off deletes nothing, so the arrangement must keep the
key and the page must simply not render it, which is what
keys_for()already does for akey it does not recognise;
named as one and given an entry point when third parties actually arrive.
The last is the cheap answer and probably the right one for this release, given the
suggestion puts extension in a later one.
The shared
Sourcespass is worth keeping. Whatever the grid does, it must not end upbuilding each widget in isolation; several of them exist only because the expensive answer
is computed once.
Every new string is thirty-nine translations. An arrange page rebuilt around a grid will
add labels, instructions and announcements. That is the standing cost of any interface work
here and it belongs in the estimate rather than in a surprise at the end.
The premise under “internal widgets plugin” has changed. This issue offered a cheap answer — name the existing registry as a plugin and give it an entry point when third parties arrive — and #129 makes that answer unavailable: a plugin Postulo ships must carry its own manifest, its own catalogues and no reach into core.
So the widget kind now inherits the same three questions: what a widget may import, where its strings live, and (if a widget ever stores anything) what happens to that when it goes. Linked as a dependency rather than left to contradict this issue quietly.
Done in
078be627, and with it the three-issue set: #123 settled where anarrangement is stored, #124 how a widget is moved without a mouse, and this is the grid and
the gesture.
The issue said three things should be decided before anything was built on them. Here are
the three answers, since they are the whole of the design.
Four columns, not five
Four. The three widths every widget already declares — a quarter, a half, a whole row —
mean what they say in four and mean nothing in five. A half-width widget in five columns is
two columns or three and never half, so five would have meant re-measuring every widget
against a grid that divides by nothing. Four took a quarter, a half and a whole with no
arithmetic and no renaming, which is a test in itself (
test_the_widths_did_not_have_to_be_renamed).Packed, not placed
A widget has a width and a place in the order; the row it lands on falls out of the two.
That is worth stating for what it makes impossible rather than for what it does. A
coordinate model — row and column per widget — can represent two widgets in one cell and a
hole in the middle of the page, so it needs validating, repairing, and an answer for what
happens when a plugin is uninstalled and its widget vanishes from row 2. This model cannot
represent either state, so none of that code exists and none of it can rot. Uninstall a
plugin and the rest close up.
A phone gets one column, in reading order
#73 is open on the phone and this does not assume it solved. What a narrow screen gets is
the same arrangement read downwards — the only reading that needs no second answer, and
there is deliberately no
sm:breakpoint offering a different one for a middle size.Dragging, on top of the arrows
The gesture asked for, added the way every drag in this application is added: on top of a
control that works everywhere, never in place of one. Drag and drop fires on neither a
touch screen nor a keyboard.
No new endpoint, no second store — a page arranged by dragging and a page arranged by
pressing arrows are the same page, saved the same way.
Because it is the same move.
draggableis set inapp.js, notin the template, so with scripts blocked nothing looks draggable. An affordance that does
nothing is worse than none.
meaning last, not somebody making a mistake.
Two things deliberately not done
Widths are not the person's to choose. The issue does not ask for it, and a width is the
widget's own statement about how much room it needs to be legible — a six-stage funnel is
unreadable in a quarter of a row. What is being arranged is the order.
"Internal widgets plugin" is the registry that already exists, named as one — not a plugin
kind. Making a widget a plugin kind would put seventeen rows in Server settings →
Plugins for an administrator to switch off, which is a wall rather than an improvement, and
would need an entry-point contract before anybody outside is asking for one. The half of
that contract that actually matters — a key that cannot collide with another provider's,
plus
Widget.provider— shipped with #123, so a plugin's widget is already namespaced andalready refused if it is not. There is a test recording that reasoning
(
test_widgets_are_a_registry_named_as_a_plugin_rather_than_a_plugin_kind), so the decisionis in the repository rather than only here.
One thing the tests caught
test_the_spans_are_written_out_rather_than_assembledfailed on the first run:col-span-4was not in the compiled stylesheet. The committed CSS was stale after the grid change —
which is exactly the silent failure the issue warned about, since Tailwind only compiles
classes it can see and a class it never sees does nothing without saying so.
npm run build:cssfixed it, and the test exists so that it fails loudly next time instead.Also
tests/test_dashboard_grid.py(18) andtests/e2e/test_widget_drag.py(7). Suite 4482passed, 29 skipped; browser suite 85 passed. No new strings — the drop reuses the
sentence the arrows already had — so all 39 European catalogues were already complete.
Wiki: Insights → The grid it lands on.
Shipped on
0.3.0, withmainkept level.