Feat v0.5.0 realtime primitive + dialog audit + permissions UI
All checks were successful
CI/CD / detect-changes (pull_request) Successful in 18s
CI/CD / test-frontend (pull_request) Successful in 5s
CI/CD / test-go-pg (pull_request) Successful in 2m42s
CI/CD / test-sqlite (pull_request) Successful in 2m59s
CI/CD / build-and-deploy (pull_request) Successful in 1m24s

Add realtime pub/sub: Starlark realtime.publish() module gated by
new realtime.publish permission, WS room protocol (room.subscribe/
room.unsubscribe intercepted in readPump with 100-room cap), and
SDK sw.realtime.subscribe() with auto room join/leave and reconnect
recovery.

Migrate 5 bare confirm() calls to sw.confirm() with destructive
styling in tasks, schedules, notes (×2), and editor packages.

Add admin permissions UI: per-permission grant/revoke drawer,
Grant All bulk action, pending_review/suspended status badges,
disabled enable toggle when pending. Closes v0.4.7 permissions
UI gap.

8 new Go tests, all passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-30 10:40:24 +00:00
parent eb9a2d7d27
commit 0af1c51ae9
21 changed files with 680 additions and 22 deletions

View File

@@ -372,8 +372,8 @@
// ── File Operations ─────────────────────────
async function _deleteFile(wsId, path, fileTree, codeEditor) {
const ok = typeof showConfirm === 'function'
? await showConfirm('Delete ' + path + '?')
const ok = typeof sw !== 'undefined' && sw.confirm
? await sw.confirm('Delete ' + path + '?', { destructive: true })
: window.confirm('Delete ' + path + '?');
if (!ok) return;
try {

View File

@@ -811,7 +811,7 @@
}
async function handleArchive() {
if (!note || !confirm('Archive this note?')) return;
if (!note || !await sw.confirm('Archive this note?', { destructive: true })) return;
await api.del('/notes/' + note.id);
onDelete();
}
@@ -1717,7 +1717,7 @@
} catch (e) { console.error('Rename folder failed:', e); }
}
async function handleDeleteFolder(folderId) {
if (!confirm('Delete this folder? Notes will be moved to Unfiled.')) return;
if (!await sw.confirm('Delete this folder? Notes will be moved to Unfiled.', { destructive: true })) return;
try {
await api.del('/folders/' + folderId);
if (activeFolderId === folderId) setActiveFolderId(null);

View File

@@ -275,7 +275,7 @@
}
async function handleDelete(item) {
if (!confirm('Delete schedule "' + item.name + '"?')) return;
if (!await sw.confirm('Delete schedule "' + item.name + '"?', { destructive: true })) return;
try {
await apiDelete(item.id);
sw.toast('Schedule deleted', 'success');

View File

@@ -144,7 +144,7 @@
}
async function handleDelete(id) {
if (!confirm('Delete this task?')) return;
if (!await sw.confirm('Delete this task?', { destructive: true })) return;
await api.del('/items/' + id);
onRefresh();
}