# Development guide and guardrails

## Scope

`@gamaraan/desktop-notify` is a pi-only extension port of the notification
stack from Oh My Pi. Do not modify pi-mono or Oh My Pi as part of this package.
The complete task blueprint and acceptance checklist are in
[`IMPLEMENTATION_PLAN.md`](./IMPLEMENTATION_PLAN.md).

## Architecture

- `src/runtime.ts`: injected Node side-effect boundary. Production uses
  `child_process.spawn` with detached, ignored stdio, an error listener, and
  `unref()`.
- `src/which.ts`: vendored PATH/PATHEXT lookup. It never invokes a shell,
  `where.exe`, or a synchronous subprocess.
- `src/platform.ts`: WSL-before-Linux detection with a fault-tolerant
  `/proc/version` seam.
- `src/notify-protocol.ts`: plain-object validation, length limits, and C0/C1
  sanitization. This is the single untrusted-input boundary.
- `src/notify-{linux,macos,windows,wsl}.ts`: independent desktop strategies.
  Linux preserves OMP's `notify-send` then `gdbus` argv contract; the other
  strategies are zero-install extensions.
- `src/osc99.ts`, `src/mux.ts`, `src/terminal-detect.ts`: terminal formatting,
  multiplexer routing, and conservative environment detection.
- `src/send.ts`: suppression → validation → cmux → one stdout write → BEL-only
  desktop fan-out.
- `src/client.ts`: public API with no pi imports, flags, handlers, or tool
  registration side effects.
- `src/index.ts`: pi flags, lifecycle handlers, EventBus subscription, and
  optional tool registration.

## OMP mapping and deliberate adaptations

| OMP responsibility | This package |
| --- | --- |
| `terminal-capabilities.ts` | protocol, OSC, mux, and terminal-detect modules |
| `desktop-notify.ts` | Linux strategy plus cross-platform dispatcher |
| `event-controller.ts` completion/error handlers | `triggers/agent-loop.ts` |
| `@oh-my-pi/pi-utils` `$which`/env helpers | `runtime.ts` and `which.ts` |
| ProcessTerminal OSC 99 probe | deliberately absent; explicit `PI_NOTIFY_OSC99_RICH=1` only |
| OMP Bun spawning | Node-standard detached spawn seam |

Never add a raw stdin capability probe: pi's TUI owns terminal input.

## Suppression and safety matrix

1. `PI_NOTIFICATIONS=off|0|false` suppresses all output.
2. Non-TTY output suppresses all output and all child processes.
3. Invalid payloads, arrays, inherited-property objects, and overlong fields are
   ignored.
4. `PI_NO_DESKTOP_NOTIFY=1` suppresses only desktop fan-out.
5. `PI_NOTIFY_PLATFORM=off` is equivalent to desktop fan-out suppression;
   `auto` detects the host, and a valid platform override wins.
6. Only BEL invokes desktop fan-out. OSC 9/99 already carry an in-band toast.
7. All terminal line protocols replace C0/C1 controls. OSC 99 rich payloads
   base64-encode unsafe chunks. AppleScript and PowerShell receive encoded or
   escaped data only.
8. cmux surface IDs are validated UUIDs and title/body are separate argv items.
9. No strategy waits for child exit, retries after a post-spawn failure, or
   installs a helper.

## EventBus contract

The stable cross-extension channel is `desktop-notify:request`. Payloads are a
string or a plain object with documented string fields. The sender must keep
its existing in-UI notification and may emit only from TUI behavior. This
extension's handler swallows all errors. A missing listener is a no-op. Do not
add a required peer, static sibling dependency, inline import, or dynamic
import to ask-tool or todos-tool.

## Coding and testing rules

- Use strict TypeScript and keep side effects behind `Runtime`.
- Add a unit/regression test for every new behavior or security boundary.
- Use synthetic runtimes for platform, PATH, filesystem, TTY, stdout, and child
  outcomes. Tests must not patch process globals or execute real subprocesses.
- Run `bun run typecheck`, `bun test`, and `bun run verify:package` before a PR.
- For Linux parity, compare OSC/mux bytes and `notify-send`/`gdbus` argv with
  the corresponding OMP tests, changing only the application name to `pi`.

## CI and release guardrails

PR CI runs typecheck, tests, and package allow-list verification. The publish
workflow runs only on `v*` tags, verifies that the tag matches `package.json`,
and requires the tag to be reachable from `origin/main` before npm provenance
publishing. Keep action pins and `package-manager-cache: false` settings
aligned with the workflows in this repository.

Release sequence: create `release/vX.Y.Z` from `main`, update the version and
lockfile, open a PR, wait for review/merge, then tag the merged main commit.
Never create a feature-branch release tag. `NODE_AUTH_TOKEN` is a GitHub secret;
never place it in source or logs.

## Manual native smoke blocker

Mock tests validate routing only. Before release, test a real TUI where
available:

1. Linux session bus: notify-send path and gdbus fallback.
2. macOS: osascript delivery.
3. Native Windows: PowerShell WinRT best-effort behavior.
4. WSL: host PowerShell preference and Linux fallback.
5. Kitty/OSC 99, OSC 9 terminals, BEL terminals, tmux, Zellij, and cmux.
6. One completion, one final error after a retry, aborted suppression, repeated
   toasts without terminal corruption, and no output in print/json/RPC modes.

Record OS, terminal, multiplexer, strategy, and result. A failed native toast
on a locked-down host is a documented limitation, not permission to weaken
input encoding or fallback guarantees.

## Future improvements

- Replace explicit OSC 99 opt-in with a pi-owned, TUI-safe capability API.
- Add native macOS UserNotifications and a better Windows identity strategy
  without installing third-party modules.
- Add richer action callbacks once pi exposes a safe notification event API.
- Implement the planned ask-tool waiting and todos-tool completion/blocked
  EventBus emitters in a separate PR; both must remain standalone.
- Add cross-platform CI runners and attach signed native smoke results to
  releases.
- Revisit strategy observability only if it can remain non-blocking and avoid
  leaking user notification content.
