singularity-forge/src/resources/extensions/sf/bootstrap/notify-interceptor.js
Mikael Hugo d33e30e885 feat(notifications): NOTICE_KIND enum, schema v2 dedup, sf-db cleanup
- notification-store: schema v2 — repeatCount/lastTs merge for non-blocking
  notices; NOTICE_KIND enum (SYSTEM_NOTICE, TOOL_NOTICE, BLOCKING_NOTICE,
  USER_VISIBLE) for renderer classification without message parsing
- sf-db: remove gate_runs and audit_events tables (replaced by uok audit.js
  and trace-writer); schema reduced by ~370 lines
- notify-interceptor: tag auto-mode system notices with NOTICE_KIND.SYSTEM_NOTICE
- auto-prompts, guided-flow, system-context: use NOTICE_KIND on emit calls
- cli-status: expanded headless status surface + test coverage
- headless-types: new status fields
- Makefile/justfile: dev workflow improvements
- record-promoter, requirement-promoter: minor cleanup
- sf-db-migration tests: updated for dropped tables
- uok-gate-runner, uok-metrics, uok-outcome, uok-status tests: updated

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-10 20:13:58 +02:00

41 lines
1.8 KiB
JavaScript

// SF Extension — Notify Interceptor
// Wraps ctx.ui.notify() in-place to persist every notification through the
// notification store. Uses a WeakSet to prevent double-wrapping and handle
// UI context replacement on /reload gracefully.
import { appendNotification } from "../notification-store.js";
import { logWarning } from "../workflow-logger.js";
// Track which ui context objects have been wrapped to prevent double-install.
// WeakSet allows GC to collect replaced uiContext instances after /reload.
const _wrappedContexts = new WeakSet();
/**
* Install the notify interceptor on a context's UI object.
* Mutates ctx.ui.notify in place — the original is called after persistence.
* Safe to call multiple times; no-ops if already installed on the same ui object.
*
* Optional third-arg metadata for durable hygiene:
* - dedupe_key — stable merge identity across wording/timer drift
* - noticeKind — NOTICE_KIND.* (system_notice, tool_notice, …)
* - merge — false to force a new JSONL row even when duplicate
*/
export function installNotifyInterceptor(ctx) {
if (_wrappedContexts.has(ctx.ui)) return;
const originalNotify = ctx.ui.notify.bind(ctx.ui);
ctx.ui.notify = (message, type, metadata) => {
try {
appendNotification(message, type ?? "info", "notify", metadata);
} catch (err) {
// Non-fatal — never let persistence break the UI.
// Include a correlation ID (timestamp + truncated message) so the
// failure can be matched against the notification that was dropped.
const correlationId = `${Date.now()}-${message.slice(0, 40).replace(/\s+/g, "_")}`;
logWarning(
"scaffold",
`notification persistence failed (non-fatal) [corr:${correlationId}]: ${err.message}`,
);
}
originalNotify(message, type, metadata);
};
_wrappedContexts.add(ctx.ui);
}