Agent skill
script-kit-notifications
Work with Script Kit's unified notification system supporting toasts, HUDs, banners, and system notifications. Use when implementing user feedback, status updates, progress indicators, or any transient messages. Triggers: "notification", "toast", "HUD", "alert", "feedback", "progress notification", "show message".
Install this agent skill to your Project
npx add-skill https://github.com/johnlindquist/script-kit-next/tree/main/.opencode/skill/script-kit-notifications
SKILL.md
Script Kit Notifications
Unified notification system with multiple delivery channels, deduplication, rate limiting, and history.
Architecture Overview
NotificationService (GPUI Global)
├── Active notifications with timer state
├── History (max 100 entries)
├── Rate limiting per source
└── Do Not Disturb mode
Channels:
├── InAppToast → gpui-component Notification (top-right stack)
├── InAppHud → HudManager (bottom-center overlay)
├── InAppBanner → Future: banner bar
├── InAppInline → Future: inline messages
├── System → Future: macOS notifications
└── Dialog → Future: blocking dialogs
Quick Start
Initialize at App Startup
NotificationService::init(cx);
Show Notifications
// Via update_global (preferred)
cx.update_global::<NotificationService, _>(|service, cx| {
service.success("Task completed!", cx);
service.error("Failed to save", cx);
service.warning("Low disk space", cx);
service.info("Update available", cx);
service.hud("Copied!", cx); // Brief overlay
});
Builder Pattern for Custom Notifications
let notif = Notification::new()
.content(NotificationContent::Rich {
icon: IconRef::parse("lucide:check-circle"), // Returns Option<IconRef>
title: "Build Complete".to_string(),
message: Some("23 files compiled".to_string()),
})
.channel(NotificationChannel::InAppToast)
.duration(Duration::from_secs(5))
.priority(NotificationPriority::High)
.action("Open", "open")
.primary_action("View Logs", "view-logs")
.from_script("/path/to/build.ts")
.dedupe("build-complete");
cx.update_global::<NotificationService, _>(|service, cx| {
service.notify(notif, cx);
});
Notification Types
Content Variants
NotificationContent::Text(String) // Simple text
NotificationContent::TitleMessage { title, message } // Title + body
NotificationContent::Rich { icon: Option<IconRef>, title, message: Option<String> } // Icon + title + optional body
NotificationContent::Progress { title, progress, message } // Progress bar
NotificationContent::Html(String) // HTML (HUD only)
Factory Methods
| Method | Icon | Duration | Priority |
|---|---|---|---|
Notification::success(msg) |
check-circle | 3s | Normal |
Notification::error(msg) |
x-circle | 5s | High |
Notification::warning(msg) |
alert-triangle | 4s | Normal |
Notification::info(msg) |
info | 3s | Normal |
Notification::hud(msg) |
none | 2s | Normal |
Notification::progress(title, 0.5) |
none | persistent | Normal |
HUD Manager (System Overlay)
For brief, non-intrusive feedback like Raycast's showHUD().
Direct Usage
use crate::hud_manager::{show_hud, show_hud_with_action, HudAction};
// Simple HUD
show_hud("Copied!".to_string(), Some(2000), cx);
// HUD with action button
show_hud_with_action(
"Log saved".to_string(),
Some(3000),
"Open".to_string(),
HudAction::OpenFile(PathBuf::from("/tmp/log.txt")),
cx,
);
HUD Actions
HudAction::OpenFile(PathBuf) // Open in configured editor
HudAction::OpenUrl(String) // Open in default browser
HudAction::RunCommand(String) // Execute shell command
HUD Behavior
- Position: Bottom-center of screen containing mouse cursor
- Stacking: Up to 3 simultaneous HUDs, stacked vertically
- Queuing: Additional HUDs queue until slots free
- Styling: Transparent pill with theme colors
- Click-through: Plain HUDs ignore mouse; action HUDs receive clicks
Toast Manager
For in-app toast notifications (staging queue for gpui-component).
Usage Pattern
let mut toast_manager = ToastManager::new();
// Push toasts from anywhere
let id = toast_manager.push(Toast::new("Message", colors).duration_ms(Some(5000)));
// In render loop, drain to gpui-component
for pending in toast_manager.drain_pending() {
window.push_notification(/* convert pending to Notification */);
}
// Periodic cleanup
toast_manager.tick(); // Auto-dismiss expired
toast_manager.cleanup(); // Remove dismissed from memory
Toast Lifecycle
push()→ Assigned UUID, added to queuedrain_pending()→ Moved to gpui-component renderertick()→ Checks timers, marks expired as dismissedcleanup()→ Removes dismissed toasts from memory
Deduplication & Replacement
Deduplication
Prevent duplicate notifications within a short window:
// Same dedupe_key = only first shows, others increment count
let notif = Notification::new()
.content(NotificationContent::Text("File changed".into()))
.dedupe("file-watcher");
// Access dedupe count for display
if let Some(active) = service.get(id) {
println!("Occurred {} times", active.dedupe_count + 1);
}
Replacement
Replace existing notifications with same key:
// Progress updates replace each other
let notif = Notification::progress("Downloading...", 0.5)
.with_replace_key("download-progress");
// Script-based notifications auto-set replace_key
let notif = Notification::info("Building...")
.from_script("/scripts/build.ts"); // replace_key = "script:/scripts/build.ts"
Rate Limiting
Per-source rate limiting (250ms window) prevents notification spam:
// Rapid calls from same source get rate-limited
for _ in 0..10 {
service.notify(Notification::info("Update").from_script("/fast.ts"), cx);
}
// Only first notification shows; rest are silently dropped
Timer Control
Pause/resume timers when window visibility changes:
// Window hidden
cx.update_global::<NotificationService, _>(|service, _| {
service.pause_timers();
});
// Window shown
cx.update_global::<NotificationService, _>(|service, _| {
service.resume_timers();
});
Do Not Disturb
cx.update_global::<NotificationService, _>(|service, _| {
service.enable_dnd(); // Suppress all except Urgent priority
service.disable_dnd();
service.toggle_dnd();
if service.is_dnd_enabled() { /* ... */ }
});
// Urgent notifications bypass DND
let urgent = Notification::error("Critical!")
.priority(NotificationPriority::Urgent);
Query Methods
cx.update_global::<NotificationService, _>(|service, _| {
// Active notifications
let active = service.active_notifications();
let count = service.active_count();
let has_any = service.has_active();
// Visible toasts (max 3)
let toasts = service.visible_toasts();
let overflow = service.overflow_toast_count();
// History
let history = service.history();
// By ID
if let Some(notif) = service.get(id) {
println!("Dedupe count: {}", notif.dedupe_count);
}
});
Progress Notifications
// Show progress
let id = cx.update_global::<NotificationService, _>(|service, cx| {
service.progress("Downloading...", 0.0, cx)
});
// Update progress
cx.update_global::<NotificationService, _>(|service, _| {
service.update_progress(id, 0.5, Some("50% complete".into()));
});
// Complete (dismiss replaces it)
cx.update_global::<NotificationService, _>(|service, cx| {
service.dismiss(id, DismissReason::Programmatic, cx);
service.success("Download complete!", cx);
});
Key Files
| File | Purpose |
|---|---|
src/notification/mod.rs |
Module exports |
src/notification/types.rs |
Core types: Notification, channels, content |
src/notification/service.rs |
NotificationService GPUI Global |
src/toast_manager.rs |
ToastManager queue for gpui-component |
src/hud_manager.rs |
HudManager for system overlays |
Constants
// NotificationService
const MAX_HISTORY_SIZE: usize = 100;
const MAX_VISIBLE_TOASTS: usize = 3;
const RATE_LIMIT_WINDOW_MS: u64 = 250;
// HudManager
const DEFAULT_HUD_DURATION_MS: u64 = 2000;
const MAX_SIMULTANEOUS_HUDS: usize = 3;
const HUD_STACK_GAP: f32 = 45.0;
const HUD_WIDTH: f32 = 200.0;
const HUD_HEIGHT: f32 = 36.0;
// ToastManager
const DEFAULT_MAX_VISIBLE: usize = 5;
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
Generate Component Documentation
Based on existing docs styles and specific API implementations, and referencing same name stories, generate comprehensive documentation for the new component.
Generate Component Story
Generate a comprehensive story for a new component for as example.
new-component
How to write a new component of GPUI Component.
troubleshooting
Diagnose and fix common Script Kit issues. Use when the user reports bugs, crashes, missing features, or unexpected behavior in Script Kit GPUI.
script-authoring
Create and manage TypeScript scripts for Script Kit. Use when the user wants to write a new script, edit an existing script, or understand Script Kit's SDK and metadata system.
agents
Create mdflow-backed agent files for Script Kit. Use when the user wants to create AI agents, configure agent backends (Claude, Gemini, Codex), or manage agent metadata.
Didn't find tool you were looking for?