Optional sections: checkpoint tracker, admin/roles, login page

- dashboard.html: each section sits in OPTIONAL:<name> BEGIN/END blocks in
  the style, markup and script, with sample answers so a page with no bot
  shows it working. Core change: setUser records the role on <body>, so CSS
  hides admin-only parts from operators.
- templates/sections.py keeps the chosen sections and deletes the rest,
  markers included; it stops on a broken marker.
- templates/sections.md: what each section needs from the host (events,
  actions, desktop slot snippets, the role re-check, the login hand-off).
- SKILL.md step 5 asks one multi-select question and runs sections.py.
- Page check: checkpoint states, admin visibility by role and assign/remove,
  login theme and hand-off to auth or the stub. Cases skip on a generated
  page without the section; the template must carry all three. The harness
  signs in when the page has a login screen.
- tests/test_sections.py: no sections leave no trace, and that page still
  passes the page check.
- Eval runner: a-empty-sections variant (all three) and per-section checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
VasanthandClaude Opus 5.5 committed 2026-10-16 14:31:25 +05:30
1 parent 3eb3fdf00e
commit 9a5b95448d
10 files changed
+536 -10

No files matched your search

+106
View File
@@ -0,0 +1,106 @@
# Optional sections
Three extras live in `dashboard.html` as marked blocks (`OPTIONAL:<name> BEGIN` ... `END`)
in the style, the markup and the script. Build only the ones the user picked:
```
python <skill>/templates/sections.py <project>/dashboard/dashboard.html checkpoints admin login
```
Name only the chosen sections; name none to remove all three. The script deletes every
unchosen block (markup, script and style) and the marker lines of the chosen ones, and it
stops if a marker is broken. Never cut the blocks by hand.
With `SAMPLE_DATA = true`, every section works on its own sample answers. Once the bot is
wired, the host must answer the section's actions as below. Desktop hosts add the slots to
the `Bridge` class in `bridge.py`. Web hosts serve `POST {API_BASE}/<action-with-dashes>`,
like the core actions, and send the events in the `/run/status` events list.
## Checkpoint tracker (`checkpoints`)
Numbered boxes on the Run tab. Each one is pending, in progress, done or stuck.
- Fill `{{CHECKPOINTS_JSON}}` with the checkpoint names, in order, from the bot's real
stages, for example `["Open the portal","Load the list","Process items","Finish up"]`.
They can be finer than `STEPS`.
- Event `cpstate {kind: "pending"|"active"|"done"|"stuck", cp}`. `cp` counts from 1.
`active` also marks the earlier boxes done and the later ones pending, so the bot can
send `active` for each checkpoint as it starts, and `stuck` when one fails.
- Desktop: add a marker table next to `STEP_MARKERS` in `bridge.py`, and send the event
from `_on_line`, using specific substrings the bot really prints:
```python
CHECKPOINT_MARKERS = [("[cp 1]", "active", 1), ("[cp 1 failed]", "stuck", 1)] # (substring, kind, cp)
# in _on_line, after the step markers:
for marker, kind, cp in CHECKPOINT_MARKERS:
if marker in low:
self.send_to_js("cpstate", {"kind": kind, "cp": cp})
break
```
## Admin/roles screen (`admin`)
An Admin tab where an admin assigns or removes admin and operator access. The Admin nav
item and the screen appear only while `set_user` says `role: "admin"`; operators never see
them. Hiding them is not security: **the host re-checks that the signed-in user is an admin
on every admin action**, and refuses otherwise.
- Event `admin_data {rows: [{username, emp_id, role: "admin"|"operator"}], msg?}`. `msg` is
shown above the list, for example "Access assigned." or "Only admins can do that."
- Actions: `load_admin()` when the tab opens, `assign_role(who, role)`, `remove_role(who)`.
`who` is a username or employee ID. Answer each one with a fresh `admin_data`.
- Desktop slots, using the project's own user store:
```python
@pyqtSlot()
def load_admin(self):
if self.role != "admin": # self.role: set when the user signs in
return
self.send_to_js("admin_data", {"rows": read_access_list()})
@pyqtSlot(str, str)
def assign_role(self, who, role):
if self.role == "admin" and role in ("admin", "operator"):
save_access(who, role)
self.load_admin()
@pyqtSlot(str)
def remove_role(self, who):
if self.role == "admin":
delete_access(who)
self.load_admin()
```
## Login page (`login`)
A sign-in screen in the page's own theme, shown over the dashboard until sign-in succeeds,
plus a Sign out button in the top bar. It is UI only: it hands the username and password to
the project's existing auth. Don't build an auth backend.
- Desktop action `login(username, password)`. The host checks them and sends
`login_result {ok, msg?, user?: {username, emp_id, role}}`. On `ok` the screen closes and
`user` fills the sidebar (and the role, if the admin screen is built). Otherwise `msg`
shows under the form.
- Web: `POST {API_BASE}/login` with JSON `{username, password}`, answered with the same
`{ok, msg?, user?}` body.
- Action `logout()` when the user signs out. The screen comes back.
- The page sits behind the screen, but the host must still refuse data actions until
sign-in succeeds.
- No auth in the project yet: keep a stub that lets any non-empty username in, and tell the
user it is a stub. In sample mode the page has its own stub.
```python
@pyqtSlot(str, str)
def login(self, username, password):
role = check_login(username, password) # the project's auth: a role, or None
self.role = role or ""
if role:
self.send_to_js("login_result", {"ok": True, "user": {"username": username, "emp_id": "", "role": role}})
else:
self.send_to_js("login_result", {"ok": False, "msg": "Wrong username or password."})
@pyqtSlot()
def logout(self):
self.role = ""
```