diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b86abc..fb88964 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,53 @@ # Changelog +## [0.37.15.0] — 2026-03-23 + +### Summary + +Workflow Ownership & Lifecycle (CS1–CS5). Adds instance cancel with assignment +cascade, assignment unclaim/reassign/cancel operations, full FE API domain +wiring for stage CRUD and assignments, team-admin workflows rewrite with +tabbed layout (Workflows + Stage Editor | Assignments Queue | Instance Monitor), +and settings assignments view replacing the read-only workflow list. + +### Added + +- **Instance cancel:** `POST /channels/:id/workflow/cancel` sets + `workflow_status='cancelled'` and cascades to all open assignments. Auth: + instance owner, team admin, or global admin. Team-scoped variant at + `POST /teams/:teamId/workflows/monitor/instances/:channelId/cancel`. + (`server/handlers/workflow_instances.go`, `server/store/*/channel.go`) +- **Assignment unclaim:** `POST /workflow-assignments/:id/unclaim` returns + claimed assignment to unassigned. Auth: claimer or team admin. + (`server/handlers/workflow_assignments.go`, `server/store/*/workflows.go`) +- **Assignment reassign:** `POST /workflow-assignments/:id/reassign` changes + `assigned_to` on claimed assignment. Auth: team admin. + (`server/handlers/workflow_assignments.go`, `server/store/*/workflows.go`) +- **Assignment cancel:** `POST /workflow-assignments/:id/cancel` cancels a + single assignment. Auth: team admin. + (`server/handlers/workflow_assignments.go`, `server/store/*/workflows.go`) +- **FE API domains:** `workflowAssignments` domain (mine, claim, unclaim, + complete, reassign, cancel, comment). Team domain additions: assignments, + workflowInstances, cancelWorkflowInstance, stage CRUD (workflowStages, + createWorkflowStage, updateWorkflowStage, deleteWorkflowStage, + reorderWorkflowStages). (`src/js/sw/sdk/api-domains.js`) +- **Team-admin workflows tabs:** Tabbed layout (Workflows | Assignments | + Monitor). Workflows tab includes inline stage editor with add/edit/delete. + Assignments tab shows claimed + available queue with claim/release/complete + actions and WS live updates. Monitor tab shows active instances with cancel. + (`src/js/sw/surfaces/team-admin/workflows.js`) +- **Settings assignments:** Replaces read-only workflow list with personal + assignment queue across all teams. Shows claimed and available assignments + with claim/release/open/complete actions. + (`src/js/sw/surfaces/settings/workflows.js`) + +### Changed + +- Settings sidebar label "Workflows" → "Assignments". + (`src/js/sw/surfaces/settings/index.js`) + +--- + ## [0.37.14.23] — 2026-03-23 ### Summary diff --git a/VERSION b/VERSION index 1ab5ac2..fb9f0e5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.37.14.23 +0.37.15.0 diff --git a/packages/build.sh b/packages/build.sh index 1087006..603d804 100644 --- a/packages/build.sh +++ b/packages/build.sh @@ -32,11 +32,13 @@ build_package() { local out="$DIST_DIR/${name}.pkg" rm -f "$out" - # Zip whichever standard dirs exist alongside manifest.json + # Zip whichever standard dirs/files exist alongside manifest.json local dirs="" - [ -d "$dir/js" ] && dirs="$dirs js/" - [ -d "$dir/css" ] && dirs="$dirs css/" - [ -d "$dir/assets" ] && dirs="$dirs assets/" + [ -d "$dir/js" ] && dirs="$dirs js/" + [ -d "$dir/css" ] && dirs="$dirs css/" + [ -d "$dir/assets" ] && dirs="$dirs assets/" + [ -f "$dir/script.star" ] && dirs="$dirs script.star" + [ -d "$dir/migrations" ] && dirs="$dirs migrations/" (cd "$dir" && zip -qr "$out" manifest.json $dirs) diff --git a/packages/team-activity-log/css/main.css b/packages/team-activity-log/css/main.css new file mode 100644 index 0000000..0187b25 --- /dev/null +++ b/packages/team-activity-log/css/main.css @@ -0,0 +1,224 @@ +/* Team Activity Log — Surface Styles + * Uses platform CSS variables exclusively for theming. + */ + +.tal-shell { + max-width: 720px; + margin: 0 auto; + padding: 24px 16px; + display: flex; + flex-direction: column; + gap: 16px; + min-height: 100%; +} + +/* ── Header ────────────────────────────────── */ + +.tal-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.tal-header__left { + display: flex; + align-items: baseline; + gap: 10px; +} + +.tal-title { + font-size: 20px; + font-weight: 600; + color: var(--text); + margin: 0; +} + +.tal-subtitle { + font-size: 13px; + color: var(--text-3); +} + +/* ── Entry Form ────────────────────────────── */ + +.tal-form { + display: flex; + gap: 8px; + background: var(--bg-surface); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: 10px 12px; +} + +.tal-form__select { + background: var(--bg-raised); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--text); + font-family: var(--font); + font-size: 13px; + padding: 6px 8px; + cursor: pointer; +} + +.tal-form__input { + flex: 1; + background: var(--bg-raised); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--text); + font-family: var(--font); + font-size: 14px; + padding: 6px 10px; + outline: none; + transition: border-color var(--transition); +} + +.tal-form__input:focus { + border-color: var(--accent); +} + +.tal-form__input::placeholder { + color: var(--text-3); +} + +.tal-form__btn { + background: var(--accent); + color: #fff; + border: none; + border-radius: var(--radius); + font-family: var(--font); + font-size: 13px; + font-weight: 500; + padding: 6px 16px; + cursor: pointer; + transition: background var(--transition); +} + +.tal-form__btn:hover:not(:disabled) { + background: var(--accent-hover); +} + +.tal-form__btn:disabled { + opacity: 0.5; + cursor: default; +} + +/* ── Filters ───────────────────────────────── */ + +.tal-filters { + display: flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; +} + +.tal-filter { + background: var(--bg-raised); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--text-2); + font-family: var(--font); + font-size: 12px; + padding: 4px 10px; + cursor: pointer; + transition: all var(--transition); +} + +.tal-filter:hover { + background: var(--bg-hover); + color: var(--text); +} + +.tal-filter.active { + background: var(--accent-dim); + border-color: var(--accent); + color: var(--accent); +} + +.tal-count { + margin-left: auto; + font-size: 12px; + color: var(--text-3); +} + +/* ── Entry List ────────────────────────────── */ + +.tal-entries { + display: flex; + flex-direction: column; + gap: 6px; +} + +.tal-entry { + background: var(--bg-surface); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 10px 12px; + transition: border-color var(--transition); +} + +.tal-entry:hover { + border-color: var(--border-light); +} + +.tal-entry__header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 4px; +} + +.tal-entry__category { + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.02em; +} + +.tal-entry__user { + font-size: 13px; + font-weight: 500; + color: var(--text); +} + +.tal-entry__time { + font-size: 12px; + color: var(--text-3); + margin-left: auto; +} + +.tal-entry__delete { + background: none; + border: none; + color: var(--text-3); + font-size: 16px; + cursor: pointer; + padding: 0 4px; + line-height: 1; + opacity: 0; + transition: all var(--transition); +} + +.tal-entry:hover .tal-entry__delete { + opacity: 1; +} + +.tal-entry__delete:hover { + color: var(--danger); +} + +.tal-entry__message { + font-size: 14px; + color: var(--text); + line-height: 1.5; +} + +/* ── Empty / Loading ───────────────────────── */ + +.tal-empty, +.tal-loading { + text-align: center; + padding: 40px 16px; + color: var(--text-3); + font-size: 14px; +} diff --git a/packages/team-activity-log/js/main.js b/packages/team-activity-log/js/main.js new file mode 100644 index 0000000..67b2e33 --- /dev/null +++ b/packages/team-activity-log/js/main.js @@ -0,0 +1,221 @@ +/** + * Team Activity Log — Surface Entry Point + * + * Demonstrates: + * - SDK boot + sw.* API access + * - Preact rendering into #extension-mount + * - Calling Starlark-backed ext API routes (/s/:slug/api/*) + * - sw.toast, sw.auth, sw.on (WS event subscription) + * - sw.userMenu for shell integration + * - Platform CSS variables for theming + */ +(async function () { + 'use strict'; + + var mount = document.getElementById('extension-mount'); + if (!mount) return; + + // ── Boot SDK (extension surfaces don't auto-load it) ── + var base = window.__BASE__ || ''; + var ver = window.__VERSION__ || '0'; + var slug = (window.__MANIFEST__ || {}).id || 'team-activity-log'; + + try { + if (!window.preact) { + var { h, render } = await import(base + '/js/sw/vendor/preact.module.js'); + var hooksModule = await import(base + '/js/sw/vendor/hooks.module.js'); + var htmModule = await import(base + '/js/sw/vendor/htm.module.js'); + window.preact = { h, render }; + window.hooks = hooksModule; + window.html = htmModule.default.bind(h); + } + var sdk = await import(base + '/js/sw/sdk/index.js?v=' + ver); + await sdk.boot(); + } catch (e) { + mount.innerHTML = '
SDK boot failed: ' + e.message + '
'; + return; + } + + var { html } = window; + var { useState, useEffect, useCallback, useRef } = hooks; + var { render } = preact; + + // ── API helper: call Starlark backend ── + var API_BASE = base + '/s/' + slug + '/api'; + + async function api(method, path, body) { + var token = sw.auth._getToken(); + var opts = { + method: method, + headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }, + }; + if (body) opts.body = JSON.stringify(body); + var resp = await fetch(API_BASE + path, opts); + var text = await resp.text(); + try { return JSON.parse(text); } catch (_) { return { _raw: text, _status: resp.status }; } + } + + // ── Components ───────────────────────────── + + var CATEGORIES = ['note', 'update', 'decision', 'action-item']; + + function App() { + var [entries, setEntries] = useState([]); + var [loading, setLoading] = useState(true); + var [filter, setFilter] = useState(''); + var menuRef = useRef(null); + + var load = useCallback(async function () { + var path = '/entries' + (filter ? '?category=' + encodeURIComponent(filter) : ''); + var d = await api('GET', path); + setEntries(d.data || []); + setLoading(false); + }, [filter]); + + useEffect(function () { load(); }, [load]); + + // Mount user menu + useEffect(function () { + if (menuRef.current && sw.userMenu) { + sw.userMenu(menuRef.current, { placement: 'down-right' }); + } + }, []); + + async function addEntry(category, message) { + var user = sw.auth.user; + var result = await api('POST', '/entries', { + category: category, + message: message, + username: user ? (user.display_name || user.username) : '', + created_at: new Date().toISOString(), + }); + if (result.error) { + sw.toast(result.error, 'error'); + return; + } + sw.toast('Entry added', 'success'); + load(); + } + + async function deleteEntry(id) { + await api('DELETE', '/entries/' + id); + sw.toast('Entry deleted', 'info'); + load(); + } + + return html` +${detail.description}
- `} - -