@checkstack/announcement-common 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # @checkstack/announcement-common
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - dee86ec: feat: add portal announcement system
8
+
9
+ Introduces a complete announcement system for communicating with portal users:
10
+
11
+ - **announcement-common**: Zod schemas for announcements (severity, visibility, display mode), oRPC contract with 6 procedures (public retrieval, user dismissal, admin CRUD), access rules, and `ANNOUNCEMENT_UPDATED` signal definition
12
+ - **announcement-backend**: Drizzle schema with `announcements` and `announcement_dismissals` tables, router with temporal filtering, visibility control, per-user dismissal persistence, user cleanup hook, real-time signal broadcasting on create/update/delete, and command palette registration ("Create Announcement", "Manage Announcements" with `⇧⌘A` shortcut)
13
+ - **announcement-frontend**: Admin management page with create/edit dialog, global banner component above the navbar (severity-colored, expandable markdown), dashboard cards with compact expand/collapse, admin menu link, and real-time WebSocket signal subscription for instant UI updates
14
+ - **frontend**: Integrates AnnouncementBanner into App.tsx for global visibility
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@checkstack/announcement-common",
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "import": "./src/index.ts"
8
+ }
9
+ },
10
+ "dependencies": {
11
+ "@checkstack/common": "0.6.4",
12
+ "@checkstack/signal-common": "0.1.8",
13
+ "@orpc/contract": "^1.13.14",
14
+ "zod": "^4.2.1"
15
+ },
16
+ "devDependencies": {
17
+ "typescript": "^5.7.2",
18
+ "@checkstack/tsconfig": "0.0.4",
19
+ "@checkstack/scripts": "0.1.2"
20
+ },
21
+ "scripts": {
22
+ "typecheck": "tsc --noEmit",
23
+ "lint": "bun run lint:code",
24
+ "lint:code": "eslint . --max-warnings 0"
25
+ },
26
+ "checkstack": {
27
+ "type": "common"
28
+ }
29
+ }
package/src/access.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { access } from "@checkstack/common";
2
+
3
+ /**
4
+ * Access rules for the Announcement plugin.
5
+ * Announcements don't use instance-level access (no teams/resource filtering).
6
+ * Only a single "manage" rule is needed — reading active announcements is public.
7
+ */
8
+ export const announcementAccess = {
9
+ /** Manage announcements — create, edit, and delete. Admin-only. */
10
+ manage: access(
11
+ "announcement",
12
+ "manage",
13
+ "Manage announcements — create, edit, and delete",
14
+ ),
15
+ };
16
+
17
+ /**
18
+ * All access rules for registration with the plugin system.
19
+ */
20
+ export const announcementAccessRules = [announcementAccess.manage];
package/src/index.ts ADDED
@@ -0,0 +1,23 @@
1
+ export { announcementAccess, announcementAccessRules } from "./access";
2
+ export {
3
+ announcementContract,
4
+ AnnouncementApi,
5
+ type AnnouncementContract,
6
+ } from "./rpc-contract";
7
+ export {
8
+ AnnouncementSeverityEnum,
9
+ AnnouncementVisibilityEnum,
10
+ AnnouncementDisplayModeEnum,
11
+ AnnouncementSchema,
12
+ CreateAnnouncementInputSchema,
13
+ UpdateAnnouncementInputSchema,
14
+ type AnnouncementSeverity,
15
+ type AnnouncementVisibility,
16
+ type AnnouncementDisplayMode,
17
+ type Announcement,
18
+ type CreateAnnouncementInput,
19
+ type UpdateAnnouncementInput,
20
+ } from "./schemas";
21
+ export * from "./plugin-metadata";
22
+ export { announcementRoutes } from "./routes";
23
+ export { ANNOUNCEMENT_UPDATED } from "./signals";
@@ -0,0 +1,9 @@
1
+ import { definePluginMetadata } from "@checkstack/common";
2
+
3
+ /**
4
+ * Plugin metadata for the announcement plugin.
5
+ * Exported from the common package so both backend and frontend can reference it.
6
+ */
7
+ export const pluginMetadata = definePluginMetadata({
8
+ pluginId: "announcement",
9
+ });
package/src/routes.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { createRoutes } from "@checkstack/common";
2
+
3
+ /**
4
+ * Route definitions for the announcement plugin.
5
+ */
6
+ export const announcementRoutes = createRoutes("announcement", {
7
+ manage: "/manage",
8
+ });
@@ -0,0 +1,99 @@
1
+ import { z } from "zod";
2
+ import { createClientDefinition, proc } from "@checkstack/common";
3
+ import { announcementAccess } from "./access";
4
+ import { pluginMetadata } from "./plugin-metadata";
5
+ import {
6
+ AnnouncementSchema,
7
+ CreateAnnouncementInputSchema,
8
+ UpdateAnnouncementInputSchema,
9
+ } from "./schemas";
10
+
11
+ export const announcementContract = {
12
+ // ---------------------------------------------------------------------------
13
+ // Public endpoints
14
+ // ---------------------------------------------------------------------------
15
+
16
+ /**
17
+ * Get currently active announcements.
18
+ * Public endpoint — anyone can read active announcements.
19
+ * If the caller is authenticated, dismissed announcements are excluded.
20
+ */
21
+ getActiveAnnouncements: proc({
22
+ operationType: "query",
23
+ userType: "public",
24
+ access: [],
25
+ })
26
+ .input(
27
+ z
28
+ .object({
29
+ /** If true, include dismissed announcements in the result. */
30
+ includeDismissed: z.boolean().optional(),
31
+ })
32
+ .optional(),
33
+ )
34
+ .output(z.object({ announcements: z.array(AnnouncementSchema) })),
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Authenticated user endpoints
38
+ // ---------------------------------------------------------------------------
39
+
40
+ /**
41
+ * Dismiss an announcement for the current user.
42
+ * Records a server-side dismissal so the announcement won't appear again.
43
+ */
44
+ dismissAnnouncement: proc({
45
+ operationType: "mutation",
46
+ userType: "user",
47
+ access: [],
48
+ })
49
+ .input(z.object({ announcementId: z.string() }))
50
+ .output(z.void()),
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Admin endpoints
54
+ // ---------------------------------------------------------------------------
55
+
56
+ /** List all announcements regardless of active/temporal state (admin view). */
57
+ listAllAnnouncements: proc({
58
+ operationType: "query",
59
+ userType: "authenticated",
60
+ access: [announcementAccess.manage],
61
+ }).output(z.object({ announcements: z.array(AnnouncementSchema) })),
62
+
63
+ /** Create a new announcement. */
64
+ createAnnouncement: proc({
65
+ operationType: "mutation",
66
+ userType: "authenticated",
67
+ access: [announcementAccess.manage],
68
+ })
69
+ .input(CreateAnnouncementInputSchema)
70
+ .output(AnnouncementSchema),
71
+
72
+ /** Update an existing announcement. */
73
+ updateAnnouncement: proc({
74
+ operationType: "mutation",
75
+ userType: "authenticated",
76
+ access: [announcementAccess.manage],
77
+ })
78
+ .input(UpdateAnnouncementInputSchema)
79
+ .output(AnnouncementSchema),
80
+
81
+ /** Delete an announcement permanently. */
82
+ deleteAnnouncement: proc({
83
+ operationType: "mutation",
84
+ userType: "authenticated",
85
+ access: [announcementAccess.manage],
86
+ })
87
+ .input(z.object({ id: z.string() }))
88
+ .output(z.object({ success: z.boolean() })),
89
+ };
90
+
91
+ // Export contract type
92
+ export type AnnouncementContract = typeof announcementContract;
93
+
94
+ // Export client definition for type-safe forPlugin usage
95
+ // Use: const client = rpcApi.forPlugin(AnnouncementApi);
96
+ export const AnnouncementApi = createClientDefinition(
97
+ announcementContract,
98
+ pluginMetadata,
99
+ );
package/src/schemas.ts ADDED
@@ -0,0 +1,87 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Announcement severity determines the visual styling of the announcement.
5
+ * - info: Blue/primary accent — general information
6
+ * - warning: Amber — important notice
7
+ * - critical: Red/destructive — urgent alert
8
+ */
9
+ export const AnnouncementSeverityEnum = z.enum(["info", "warning", "critical"]);
10
+ export type AnnouncementSeverity = z.infer<typeof AnnouncementSeverityEnum>;
11
+
12
+ /**
13
+ * Controls who can see the announcement.
14
+ * - all: Everyone, including unauthenticated visitors
15
+ * - authenticated: Only logged-in users
16
+ */
17
+ export const AnnouncementVisibilityEnum = z.enum(["all", "authenticated"]);
18
+ export type AnnouncementVisibility = z.infer<typeof AnnouncementVisibilityEnum>;
19
+
20
+ /**
21
+ * Controls where the announcement is displayed.
22
+ * - banner: Global strip above the navbar on every page
23
+ * - dashboard: Card in the dashboard Overview section
24
+ * - both: Displayed in both locations
25
+ */
26
+ export const AnnouncementDisplayModeEnum = z.enum([
27
+ "banner",
28
+ "dashboard",
29
+ "both",
30
+ ]);
31
+ export type AnnouncementDisplayMode = z.infer<
32
+ typeof AnnouncementDisplayModeEnum
33
+ >;
34
+
35
+ /**
36
+ * Core announcement entity schema.
37
+ */
38
+ export const AnnouncementSchema = z.object({
39
+ id: z.string(),
40
+ title: z.string(),
41
+ message: z.string(),
42
+ severity: AnnouncementSeverityEnum,
43
+ visibility: AnnouncementVisibilityEnum,
44
+ displayMode: AnnouncementDisplayModeEnum,
45
+ active: z.boolean(),
46
+ startsAt: z.date().optional(),
47
+ expiresAt: z.date().optional(),
48
+ createdBy: z.string(),
49
+ createdAt: z.date(),
50
+ updatedAt: z.date(),
51
+ });
52
+ export type Announcement = z.infer<typeof AnnouncementSchema>;
53
+
54
+ /**
55
+ * Input schema for creating an announcement.
56
+ */
57
+ export const CreateAnnouncementInputSchema = z.object({
58
+ title: z.string().min(1, "Title is required"),
59
+ message: z.string().min(1, "Message is required"),
60
+ severity: AnnouncementSeverityEnum,
61
+ visibility: AnnouncementVisibilityEnum,
62
+ displayMode: AnnouncementDisplayModeEnum,
63
+ active: z.boolean().optional().default(true),
64
+ startsAt: z.date().optional(),
65
+ expiresAt: z.date().optional(),
66
+ });
67
+ export type CreateAnnouncementInput = z.infer<
68
+ typeof CreateAnnouncementInputSchema
69
+ >;
70
+
71
+ /**
72
+ * Input schema for updating an announcement.
73
+ */
74
+ export const UpdateAnnouncementInputSchema = z.object({
75
+ id: z.string(),
76
+ title: z.string().min(1).optional(),
77
+ message: z.string().min(1).optional(),
78
+ severity: AnnouncementSeverityEnum.optional(),
79
+ visibility: AnnouncementVisibilityEnum.optional(),
80
+ displayMode: AnnouncementDisplayModeEnum.optional(),
81
+ active: z.boolean().optional(),
82
+ startsAt: z.date().optional(),
83
+ expiresAt: z.date().optional(),
84
+ });
85
+ export type UpdateAnnouncementInput = z.infer<
86
+ typeof UpdateAnnouncementInputSchema
87
+ >;
package/src/signals.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { z } from "zod";
2
+ import { createSignal } from "@checkstack/signal-common";
3
+
4
+ /**
5
+ * Broadcast when an announcement is created, updated, or deleted.
6
+ * Frontend components listening to this signal should refetch active announcements.
7
+ */
8
+ export const ANNOUNCEMENT_UPDATED = createSignal(
9
+ "announcement.updated",
10
+ z.object({
11
+ announcementId: z.string(),
12
+ action: z.enum(["created", "updated", "deleted"]),
13
+ }),
14
+ );
package/tsconfig.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "@checkstack/tsconfig/common.json",
3
+ "include": [
4
+ "src"
5
+ ]
6
+ }