diff --git a/CHANGELOG.md b/CHANGELOG.md
index 34c0e5c..628bdc9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,79 @@
# Changelog
+## [0.28.3.1] — 2026-03-14
+
+### Summary
+
+Frontend decomposition Phases 2 + 3. Phase 2: onclick→data-action
+delegation migration (143 → 5 unconvertible). Phase 3: `sb.js` action
+registry replacing all `window.*` exports with centralized
+`sb.register()`/`sb.ns()` dispatch.
+
+### Added
+
+#### Action Registry — `sb.js` (Phase 3)
+- New file `src/js/sb.js`, loaded first in `base.html`.
+- `sb.register(name, fn)` — register standalone action, dual-writes to
+ `window[name]` for backward compat until ES modules land.
+- `sb.ns(name, obj)` — register namespace object (API, UI, App, etc.),
+ methods accessible via dot notation: `sb.resolve('UI.copyMessage')`.
+- `sb.resolve(name)` — centralized action resolution for flat names
+ and dot-notation namespace methods. Falls back to `window[name]`.
+- `sb.call(name, ...args)` — template bridge for Go onclick handlers.
+- `sb.callEvent(event, name, ...args)` — event passthrough variant.
+- `sb.list()` — introspection: list all registered actions/namespaces.
+- `sb.has(name)` — check if action is registered.
+
+### Changed
+
+#### Phase 2 — onclick→data-action Delegation
+- 138 of 143 dynamic inline onclick handlers converted to `data-action`
+ + `data-args` attributes with centralized event delegation.
+- `_uiDispatch(container)`: idempotent delegated click handler wired on
+ `document.body`. Supports `data-args` (JSON), `data-pass-event`
+ (context menu positioning), `data-pass-el` (checkbox state).
+- `_ctxDispatch(container)`: idempotent delegated handler for context
+ menus and panel buttons (auto-dismisses menu after action).
+- Wrapper functions for compound expressions: `_toggleArchivedProjects`,
+ `_submitEditFromDOM`, `_copyCodeBlock`, `_openNoteFromTool`,
+ `_toggleNoteSelectCb`, `_noteItemClick`.
+- 5 unconvertible onclick remain (self-referencing DOM removal,
+ computed onclick from variable).
+
+**Files converted (onclick):** `projects-ui.js` (26→0), `ui-core.js`
+(25→1), `ui-admin.js` (28→1), `ui-format.js` (10→0),
+`notifications.js` (8→0), `notes.js` (8→0), `channel-models.js` (5→0),
+`ui-settings.js` (8→0), `files.js` (3→0), `admin-handlers.js` (2→0),
+`app.js` (4→1), `workflow-queue.js` (6→0), `workflow-admin.js` (5→0),
+`task-settings.js` (1→0), `task-sidebar.js` (3→1).
+
+#### Phase 3 — Action Registry Migration
+- 234 top-level `window.X = X` exports across 35 files converted to
+ `sb.register('X', X)` or `sb.ns('X', X)`. 28 namespace objects,
+ 206 standalone functions.
+- 14 in-body `window.X = function` assignments across 6 files
+ (`settings-handlers.js`, `task-admin.js`, `task-settings.js`,
+ `task-sidebar.js`, `workflow-admin.js`, `workflow-queue.js`)
+ converted to `sb.register('X', function)` or local const + `sb.ns()`.
+- `_uiDispatch` and `_ctxDispatch` dispatch updated to use
+ `sb.resolve()` instead of `window[]` walking.
+- `base.html` loads `sb.js` before `app-state.js`.
+- `sb.register/sb.ns` dual-writes to `window.*` for backward
+ compatibility — this side-effect is removed when files convert to
+ ES module imports (Phase 4).
+
+### Metrics
+
+| | v0.28.3 | v0.28.3.1 |
+|--|---------|-----------|
+| Dynamic inline onclick | 143 | 5 (unconvertible) |
+| data-action delegated | 0 | 163 |
+| `window.*` exports | 248 | 1 (debug fetch patch) |
+| `sb.register` calls | 0 | 220 |
+| `sb.ns` calls | 0 | 30 |
+| Action resolution | `window[]` | `sb.resolve()` centralized |
+
+
## [0.28.3] — 2026-03-13
### Summary
diff --git a/VERSION b/VERSION
index 0ba165c..a5c3d8f 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.28.3
\ No newline at end of file
+0.28.3.1
\ No newline at end of file
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index 5bb3340..1a23175 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -174,9 +174,13 @@ decomposition groundwork.
`api.js` — uses `API.accessToken` instead
- [ ] ICD runner gains test tiers: `crud`, `envelope`, `security`, `sdk`
- [ ] Runner coverage target: 100% of ICD-documented endpoints have at least one test
-- [ ] Phase 2: onclick → addEventListener migration (143 dynamic + ~50 template handlers)
-- [ ] Phase 3: ES module conversion
-- [ ] Phase 4: Template handler shim
+- [x] Phase 2: onclick → addEventListener migration (143 dynamic → 5 unconvertible,
+ 163 data-action delegated via `_uiDispatch` on document.body)
+- [x] Phase 3: Action registry (`sb.js`) — 248 `window.*` exports replaced with
+ `sb.register()`/`sb.ns()`. `sb.resolve()` centralized dispatch.
+ `sb.call()` template bridge ready for Go template migration.
+- [ ] Phase 3b: Go template onclick → `sb.call()` migration (~50 handlers)
+- [ ] Phase 4: ES module conversion (remove IIFE wrappers, add import/export)
### v0.28.4 — Security Tier (ICD Runner Red Team)
New `security` tier in ICD test runner. Multi-user fixtures already exist.
diff --git a/server/pages/templates/base.html b/server/pages/templates/base.html
index a772fcb..e25c4f7 100644
--- a/server/pages/templates/base.html
+++ b/server/pages/templates/base.html
@@ -91,6 +91,7 @@
{{if .Manifest}}window.__MANIFEST__ = {{.Manifest | toJSON}};{{end}}
+