contact messages: standalone /_m admin inbox, site-name reply label

The inbox moves out of the analytics tab to its own page at /_m (own pens-row icon, own entry and socket lifecycle; the admin-gated /_api/ws/analytics socket keeps serving both, with typed messaging frames ignored by AnalyticsView). Admin replies are labeled with the currently configured site name in the wire payload (the SSO name stays stored for audit), and the thread view is capped to roughly the visitor panel's width.
This commit is contained in:
LeoVasanko committed 2026-09-27 18:59:22 +00:00
1 parent f4d1f0ac58
commit 4ad4e3bdff
11 files changed
+535 -209

No files matched your search

+19 -16
View File
@@ -2,24 +2,24 @@
Visitor→admin messaging built into the site: a `{contact: Label}` directive
in article Markdown renders a button that opens a chat-style panel; messages
are stored in the kanta database and answered from the admin inbox on `/_a`.
are stored in the kanta database and answered from the admin inbox on `/_m`.
Visitors are anonymous — a localStorage session token identifies their
thread — and everything realtime rides the two existing WebSockets; there is
no REST polling anywhere.
- `pagerite/messages.py` — the feature's core: the strict message renderer,
thread store ops, wire payloads, both socket protocols and the
`/_contact/files` upload route.
thread store ops, wire payloads, both socket protocols, the `/_m` inbox
page route and the `/_contact/files` upload route.
- `pagerite/data.py` — `Data.contact` (`ContactThread`/`ContactMessage`).
- `pagerite/tracking.py` — the sockets the protocol rides (see below).
- `pagerite/views.py` — the `{contact:}` directive (`_contact_tag`) and the
on-demand bundle's asset config.
- `pagerite/views.py` — the `{contact:}` directive (`_contact_tag`), the
`/_m` page render and the on-demand bundles' asset config.
- `frontend/src/contact-main.js` + `ContactPanel.vue` — the visitor panel,
imported on demand by `pagerite.js` on trigger clicks.
- `frontend/src/Composer.vue` + `richtext.js` — the shared composer and its
DOM→Markdown serializer.
- `frontend/src/MessagesView.vue` — the admin inbox (the messages tab of
`AnalyticsView.vue` on `/_a`).
- `frontend/src/messages-main.js` + `MessagesView.vue` — the admin inbox
page at `/_m` (own pens-row icon, own socket lifecycle).
## Trigger
@@ -78,16 +78,18 @@ solves and retries once. Challenges are a per-connection local in the
## Admin side
Admins watch the inbox as the **messages tab on `/_a`**, served by the
existing `/_api/ws/analytics` socket (admin-gated like all of `/_api`).
Analytics payloads keep their type-less snapshot shape; messaging frames
always carry a `type` field, and that is how the client tells them apart.
On connect the server sends `{type: "threads", threads: [...]}` (100 most
Admins watch the inbox on its own page, **`/_m`** (✉️ icon in the pens
row), served by the existing `/_api/ws/analytics` socket (admin-gated like
all of `/_api`). Analytics payloads keep their type-less snapshot shape;
messaging frames always carry a `type` field, and that is how each client
picks out its own frames (the analytics viewer ignores them). On connect
the server sends `{type: "threads", threads: [...]}` (100 most
recently active); new messages and replies push single-thread updates.
Admin→server frames: `{type: "reply", id, text}` and `{type: "read", id}`.
Replies are authored with the SSO `remote-user` name and mark the thread
read; `unread` on a thread means its last message is the visitor's and
newer than the admin's last read.
Replies store the SSO `remote-user` name for audit but are labeled with
the currently configured **site name** in the wire payload (substituted at
read time) and mark the thread read; `unread` on a thread means its last
message is the visitor's and newer than the admin's last read.
## Message format and the XSS model
@@ -151,7 +153,8 @@ message, fixed afterwards) and
`messages: dict[str, ContactMessage]` keyed by a sortable timestamp-random
id (dict-keyed, so a save's database diff touches only the appended
message). Each message stores `t`, the Markdown source `md`, its strict
rendering `html`, `admin` ("" = visitor, else the SSO name) and `page`.
rendering `html`, `admin` ("" = visitor, else the SSO name — the wire
payload shows the current site name instead) and `page`.
Appends are single transactions (`contact` / `contact:reply` /
`contact:read`).