Hold a new dashboard back until someone publishes it
A dashboard went live the moment it was created — an empty document straight to the panels — while a new flow starts as a draft. It now works the way flows do: published means `dashboard.json` exists, so every dashboard on every running installation is already published and nothing needs migrating. Only the ones created from here on start as drafts. Mirroring FlowStore turned up a latent 500: discarding the draft of a dashboard that had never been published unlinked its only file, and the read that followed raised out of a 200 handler. It answers 400 now, the way a flow does. Publishing all of them was 2N requests, because a publish has to name the version it expects and the summaries did not carry one. They do now — and so do the flow summaries, which had the same defect nobody had written down. A panel had no way to hear about any of this. A publish, or a change to which dashboards a panel carries, now puts one event on the bus and the screen refetches what changed: no reload, so a wall display never blanks or asks for its credential again. The subtle half is that a socket's message allowlist was computed once at handshake — a reassigned panel would have fetched its new document and then shown tiles that never updated. The panels dialog logged non-superusers out. Every write in it needs a superuser, not only the checkboxes the report mentioned, so the dialog is read-only for everyone else. The logout itself was `main.tsx` treating 403 as a dead session, against the contract deps.py spells out: only a 401 ends a session, and a 403 now says so rather than silently signing someone out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uq8mtNb97A7praJLyeEYgs
This commit is contained in:
@@ -87,6 +87,38 @@ def test_discarding_leaves_what_is_published(store: DashboardStore):
|
||||
assert not store.has_draft("house")
|
||||
|
||||
|
||||
def test_a_new_dashboard_is_a_draft_until_it_is_published(store: DashboardStore):
|
||||
"""Creating one does not put it on a wall; publishing is what does."""
|
||||
created = store.write_draft(default_dashboard("house"), 0)
|
||||
|
||||
assert not store.is_published("house")
|
||||
with pytest.raises(DashboardNotFound):
|
||||
store.read("house")
|
||||
assert store.read("house", draft=True).title == created.title
|
||||
# Listed all the same, so the editor can find what it just made.
|
||||
assert [(d.name, d.has_draft, d.version) for d in store.list()] == [
|
||||
("house", True, created.version)
|
||||
]
|
||||
|
||||
store.publish("house", created.version)
|
||||
|
||||
assert store.is_published("house")
|
||||
assert store.read("house").title == created.title
|
||||
|
||||
|
||||
def test_an_unpublished_dashboard_can_be_renamed_and_deleted(store: DashboardStore):
|
||||
"""And renaming it does not put it on a wall either."""
|
||||
store.write_draft(default_dashboard("house"), 0)
|
||||
|
||||
store.rename("house", "home")
|
||||
|
||||
assert not store.exists("house")
|
||||
assert store.has_draft("home") and not store.is_published("home")
|
||||
|
||||
store.delete("home")
|
||||
assert not store.exists("home")
|
||||
|
||||
|
||||
def test_deleting_and_renaming(store: DashboardStore):
|
||||
store.write(default_dashboard("house"))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user