Skip to content

Conversation

@simplesagar
Copy link
Member

@simplesagar simplesagar commented Jan 14, 2026

Summary

  • Fix SDK notification persistence: Correctly wrap notification data in createNotificationForm object to match SDK schema requirements
  • Opt-in persistence model: Changed from opt-out (persist: false) to opt-in (persist: true) for explicit control over which toasts are persisted to the notification bell
  • Improved notification bell placement: Moved from sidebar to page header for better visibility across all pages
  • Widespread persist: true adoption: Added to important user action toasts (toolset changes, MCP toggles, deployments, source operations, etc.)
CleanShot.2026-01-18.at.14.07.14.mp4

Architecture

Notification System Overview

The notification system uses a frontend-driven persistence model where toasts can optionally be persisted to the database for display in the notification bell.

flowchart TB
    subgraph Frontend["Frontend (React)"]
        UA[User Action] --> TC[toast.success/error/etc]
        TC --> ST[Sonner Toast]
        TC -->|persist: true| PN[persistNotification]
        PN --> SDK[Gram SDK Client]
        
        NB[Notification Bell] -->|Poll every 30s| UC[Unread Count Query]
        NB -->|On open| NL[Notifications List Query]
    end
    
    subgraph Backend["Backend (Go)"]
        SDK -->|POST /rpc/notifications.create| NS[Notifications Service]
        UC -->|GET /rpc/notifications.unreadCount| NS
        NL -->|GET /rpc/notifications.list| NS
        NS --> DB[(PostgreSQL)]
    end
    
    ST -->|Ephemeral| User((User sees toast))
    NB -->|Persistent| User
Loading

Key Components

Component File Purpose
Toast Wrapper src/lib/toast.ts Wraps Sonner toast, optionally persists to backend
Notification Bell src/components/notification-bell.tsx UI for viewing persisted notifications
Notifications Service server/internal/notifications/impl.go API handlers for CRUD operations

How It Works

  1. Toast Creation: When code calls toast.success("message", { persist: true }):

    • Sonner displays the ephemeral toast immediately
    • If persist: true, the wrapper calls the SDK to create a notification
  2. Persistence: The SDK sends POST /rpc/notifications.create with:

    {
      type: "user_action" | "system",
      level: "info" | "success" | "warning" | "error", 
      title: string,
      resourceType?: string,  // Optional: links notification to a resource
      resourceId?: string
    }
  3. Bell Updates: The notification bell:

    • Polls /rpc/notifications.unreadCount every 30 seconds
    • Tracks "last viewed" timestamp in localStorage
    • Shows badge with unread count
    • Fetches full list only when popover opens

Design Decisions

  • Opt-in persistence: Only toasts with persist: true are saved, preventing notification spam
  • Frontend-driven: UI controls which actions create notifications, not the backend
  • Lightweight polling: 30s interval balances freshness vs server load
  • Local timestamp tracking: Avoids server-side "read" state per user

Test plan

  • Enable/disable an MCP server and verify toast appears AND persists to notification bell
  • Add/remove a tool from a toolset and verify notification appears in bell
  • Check notification bell is visible in page header (not sidebar)
  • Verify notification unread count updates correctly
  • Verify other toasts without persist: true do NOT appear in notification bell

🤖 Generated with Claude Code

@vercel
Copy link

vercel bot commented Jan 14, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
gram Ready Ready Preview, Comment Jan 17, 2026 9:04pm
gram-docs-redirect Ready Ready Preview, Comment Jan 17, 2026 9:04pm

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Jan 14, 2026

🦋 Changeset detected

Latest commit: dafd3dc

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
dashboard Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@simplesagar simplesagar marked this pull request as draft January 14, 2026 02:29
@simplesagar simplesagar changed the title feat(dashboard): improve notification persistence and UI placement feat(dashboard): improve notification persistence and add notification inbox Jan 14, 2026
@simplesagar simplesagar force-pushed the notifications_stream-H branch from 7f258d2 to 5f4d3b3 Compare January 15, 2026 00:43
@simplesagar simplesagar force-pushed the notifications_stream-H branch from b19df83 to 5408d45 Compare January 15, 2026 05:32
@github-actions
Copy link
Contributor

Squawk Report

✅ 0 violations across 1 file(s)


server/migrations/20260113213458_notifications-table.sql

-- Modify "external_mcp_tool_definitions" table
ALTER TABLE "external_mcp_tool_definitions" ALTER COLUMN "transport_type" DROP DEFAULT;
-- Create "notifications" table
CREATE TABLE "notifications" (
  "id" uuid NOT NULL DEFAULT generate_uuidv7(),
  "project_id" uuid NOT NULL,
  "type" text NOT NULL,
  "level" text NOT NULL,
  "title" text NOT NULL,
  "message" text NULL,
  "actor_user_id" text NULL,
  "resource_type" text NULL,
  "resource_id" text NULL,
  "archived_at" timestamptz NULL,
  "created_at" timestamptz NOT NULL DEFAULT clock_timestamp(),
  "updated_at" timestamptz NOT NULL DEFAULT clock_timestamp(),
  "deleted_at" timestamptz NULL,
  "deleted" boolean NOT NULL GENERATED ALWAYS AS (deleted_at IS NOT NULL) STORED,
  PRIMARY KEY ("id"),
  CONSTRAINT "notifications_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects" ("id") ON UPDATE NO ACTION ON DELETE SET NULL,
  CONSTRAINT "notifications_level_check" CHECK (level = ANY (ARRAY['info'::text, 'success'::text, 'warning'::text, 'error'::text])),
  CONSTRAINT "notifications_message_check" CHECK (char_length(message) <= 2000),
  CONSTRAINT "notifications_resource_id_check" CHECK (char_length(resource_id) <= 100),
  CONSTRAINT "notifications_resource_type_check" CHECK (char_length(resource_type) <= 50),
  CONSTRAINT "notifications_title_check" CHECK ((title <> ''::text) AND (char_length(title) <= 200)),
  CONSTRAINT "notifications_type_check" CHECK (type = ANY (ARRAY['system'::text, 'user_action'::text]))
);
-- Create index "notifications_project_id_created_at_idx" to table: "notifications"
CREATE INDEX "notifications_project_id_created_at_idx" ON "notifications" ("project_id", "created_at" DESC) WHERE (deleted IS FALSE);

✅ Rule Violations (0)

No violations found.


📚 More info on rules

⚡️ Powered by Squawk (2.36.0), a linter for PostgreSQL, focused on migrations

simplesagar and others added 15 commits January 17, 2026 11:56
- Add notifications database table with project scope
- Create Goa API service with list, create, archive, unreadCount endpoints
- Implement backend service with SQLc queries
- Create toast wrapper to persist notifications to backend
- Add NotificationBell component with Active/Archived tabs
- Integrate bell into sidebar header
- Migrate all toast imports to use new wrapper

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Fix SDK call to wrap notification data in createNotificationForm
- Change toast persistence to opt-in (persist: true) instead of opt-out
- Move notification bell from sidebar to page header for better visibility
- Fix notification-bell Date type handling for lastViewedAt
- Add persist: true to important user action toasts across the dashboard:
  - Toolset tool additions/removals
  - MCP server enable/disable
  - Deployment redeployments
  - Source operations (create, delete, environment attach)
  - Project operations (create, archive)
  - Playground API key operations
  - Feature request submissions
  - And more

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Fix import path for @gram/client/react-query (remove index.js suffix)
- Fix Stack justify prop value (between -> space-between)
- Apply Prettier formatting to modified files

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Co-Authored-By: Claude Opus 4.5 <[email protected]>
Co-Authored-By: Claude Opus 4.5 <[email protected]>
…ployment

- Use Date type for notification createdAt/archivedAt (matches SDK)
- Pass empty object instead of undefined to queryKeyNotificationsUnreadCount
- Remove function wrapper around JSX in toast.success call

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Remove chat history UI code that was incorrectly introduced during
conflict resolution. This code referenced undefined variables and
types that don't belong to this branch.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Regenerate lockfile to include dependencies from elements/package.json
that were added in main branch.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Regenerate Goa server code to sync with codebase
- Update atlas.sum after new migration from main branch

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@speakeasybot
Copy link
Collaborator

speakeasybot commented Jan 17, 2026

Preview Environment (PR #1232)

Preview environment scaled down after 0.01h of inactivity.
Re-add the preview label to restart.

@simplesagar simplesagar changed the title feat(dashboard): improve notification persistence and add notification inbox feat(dashboard): project notification persistence and add notification inbox Jan 18, 2026
@speakeasybot speakeasybot removed the preview Spawn a preview environment label Jan 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants