@gratiaos/pad-core 1.0.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.
@@ -0,0 +1,113 @@
1
+ import { getRealtimePort, getRealtimeCircleId, onRealtimePortChange } from './realtime/registry.js';
2
+ import { TOPIC_SCENES } from './realtime/port.js';
3
+ /**
4
+ * Scene Events (Pad Core)
5
+ * -------------------------------------------------------
6
+ * Typed CustomEvent helpers for entering and completing Scenes.
7
+ * Mirrors the pad events API shape (dispatch/on/... with unsubscribe).
8
+ */
9
+ export const SCENE_ENTER = 'scene:enter';
10
+ export const SCENE_COMPLETE = 'scene:complete';
11
+ function getTarget(target) {
12
+ // Default to window in the browser; otherwise fallback to a lightweight target.
13
+ if (target)
14
+ return target;
15
+ if (typeof window !== 'undefined' && window)
16
+ return window;
17
+ // Node/SSR fallback
18
+ return new EventTarget();
19
+ }
20
+ /**
21
+ * Dispatchers
22
+ * -------------------------------------------------------
23
+ */
24
+ export function dispatchSceneEnter(detail, target, options) {
25
+ const t = getTarget(target);
26
+ const payload = { timestamp: Date.now(), ...detail };
27
+ const evt = new CustomEvent(SCENE_ENTER, { detail: payload, bubbles: true });
28
+ t.dispatchEvent(evt);
29
+ if (options?.skipRealtime)
30
+ return;
31
+ try {
32
+ const rt = getRealtimePort();
33
+ const circleId = getRealtimeCircleId();
34
+ if (rt && circleId && rt.status() === 'connected') {
35
+ rt.publish(TOPIC_SCENES, { kind: 'enter', detail: payload });
36
+ }
37
+ }
38
+ catch { }
39
+ }
40
+ export function dispatchSceneComplete(detail, target, options) {
41
+ const t = getTarget(target);
42
+ const payload = { timestamp: Date.now(), ...detail };
43
+ const evt = new CustomEvent(SCENE_COMPLETE, { detail: payload, bubbles: true });
44
+ t.dispatchEvent(evt);
45
+ if (options?.skipRealtime)
46
+ return;
47
+ try {
48
+ const rt = getRealtimePort();
49
+ const circleId = getRealtimeCircleId();
50
+ if (rt && circleId && rt.status() === 'connected') {
51
+ rt.publish(TOPIC_SCENES, { kind: 'complete', detail: payload });
52
+ }
53
+ }
54
+ catch { }
55
+ }
56
+ /**
57
+ * Listeners
58
+ * -------------------------------------------------------
59
+ * Returns an unsubscribe function for convenience.
60
+ */
61
+ export function onSceneEnter(handler, target) {
62
+ const t = getTarget(target);
63
+ t.addEventListener(SCENE_ENTER, handler);
64
+ return () => t.removeEventListener(SCENE_ENTER, handler);
65
+ }
66
+ export function onSceneComplete(handler, target) {
67
+ const t = getTarget(target);
68
+ t.addEventListener(SCENE_COMPLETE, handler);
69
+ return () => t.removeEventListener(SCENE_COMPLETE, handler);
70
+ }
71
+ /**
72
+ * Realtime Bridge
73
+ * -------------------------------------------------------
74
+ * Listens for active RealtimePort changes from the registry and mirrors
75
+ * scene:enter / scene:complete messages between peers. Incoming messages
76
+ * are re-dispatched locally via DOM CustomEvents so all consumers (pads,
77
+ * monitors, tools) receive them as if they originated locally.
78
+ */
79
+ let teardownRealtimeScenes = null;
80
+ onRealtimePortChange((port) => {
81
+ if (teardownRealtimeScenes) {
82
+ try {
83
+ teardownRealtimeScenes();
84
+ }
85
+ catch { }
86
+ teardownRealtimeScenes = null;
87
+ }
88
+ if (!port)
89
+ return;
90
+ if (globalThis?.process?.env?.NODE_ENV !== 'production') {
91
+ // eslint-disable-next-line no-console
92
+ console.debug('[pad-core] realtime scenes bridge active');
93
+ }
94
+ try {
95
+ const off = port.subscribe(TOPIC_SCENES, (msg) => {
96
+ if (!msg || typeof msg !== 'object')
97
+ return;
98
+ if (msg.kind === 'enter') {
99
+ dispatchSceneEnter(msg.detail, null, { skipRealtime: true });
100
+ }
101
+ else if (msg.kind === 'complete') {
102
+ dispatchSceneComplete(msg.detail, null, { skipRealtime: true });
103
+ }
104
+ });
105
+ teardownRealtimeScenes = () => {
106
+ try {
107
+ off();
108
+ }
109
+ catch { }
110
+ };
111
+ }
112
+ catch { }
113
+ });
@@ -0,0 +1,133 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * Garden Pads — shared contracts for pads across apps (Playground, M3 UI, etc).
4
+ *
5
+ * This file contains only **types and constants**. No DOM or framework code.
6
+ * - Pad identity and metadata (PadManifest)
7
+ * - Visual tone and mood vocabulary
8
+ * - Lightweight pad signal contract (for local bus / adapters)
9
+ * - Route preferences (hash/path/query) without assuming a router
10
+ * - Optional CustomEvent detail shapes used by the event helpers
11
+ */
12
+ /** Stable identifier for a pad (kebab-case recommended). */
13
+ export type PadId = string;
14
+ /** Chrome/semantic tone a pad prefers for its header and accents. */
15
+ export type PadTone = 'accent' | 'positive' | 'warning' | 'danger' | 'neutral';
16
+ /** Stable identifier for a scene within a pad (kebab-case recommended). */
17
+ export type PadSceneId = string;
18
+ /** Alias for clarity in scene-related modules. */
19
+ export type SceneId = PadSceneId;
20
+ /** A pad's transient “mood” (used for micro-animations / ambience). */
21
+ export type PadMood = 'soft' | 'focused' | 'celebratory';
22
+ /** Optional theme nudge a pad can suggest to its host shell. */
23
+ export interface PadTheme {
24
+ /** CSS color token or hex (host decides how to interpret). */
25
+ accent?: string;
26
+ /** Background/surface token or color. */
27
+ surface?: string;
28
+ }
29
+ /**
30
+ * Lightweight intra-pad signals (kept framework-agnostic).
31
+ * Adapters can map these to DOM CustomEvents, context, Rx, etc.
32
+ */
33
+ export type PadSignal = {
34
+ type: 'PAD.MOOD.SET';
35
+ mood: PadMood;
36
+ } | {
37
+ type: 'PAD.THEME.SET';
38
+ accent?: string;
39
+ surface?: string;
40
+ } | {
41
+ type: 'PAD.EVENT.CAPTURED';
42
+ payload: {
43
+ noteId: string;
44
+ };
45
+ };
46
+ /** Listener signature for the lightweight pad signal bus. */
47
+ export type PadListener = (msg: PadSignal) => void;
48
+ /** How a pad prefers to be addressed in the URL. */
49
+ export type PadRouteMode = 'hash' | 'path' | 'query' | 'auto';
50
+ /** Default route mode for helpers that auto-detect environment. */
51
+ export declare const DEFAULT_ROUTE_MODE: PadRouteMode;
52
+ /** Default query key used when routing via ?pad=<id>. */
53
+ export declare const DEFAULT_QUERY_KEY: "pad";
54
+ /** Preferred addressing for a pad; hosts can honor any subset. */
55
+ export interface PadRouteOptions {
56
+ /**
57
+ * Preferred routing mode.
58
+ * - 'hash': use `#pad=town` (SPAs without server routing)
59
+ * - 'query': use `?pad=town` (works for SSR/CDN where hash is undesirable)
60
+ * - 'path': use `/pads/town` (requires host path routing)
61
+ * - 'auto': helper decides based on environment/capabilities
62
+ * Defaults to 'auto'.
63
+ */
64
+ mode?: PadRouteMode;
65
+ /**
66
+ * Hash parameter name used in URLs, e.g. `#pad=town`.
67
+ * Defaults to `"pad"`; hosts may override.
68
+ */
69
+ hashKey?: string;
70
+ /**
71
+ * Query parameter used when `mode: 'query'` or for 'auto' fallback, e.g. `?pad=town`.
72
+ * Defaults to `"pad"`.
73
+ */
74
+ queryKey?: string;
75
+ /**
76
+ * Preferred absolute path or leaf segment, e.g. `"/pads/town"` or `"town"`.
77
+ * Hosts may ignore this in favor of hash/query-only routing.
78
+ */
79
+ path?: string;
80
+ /**
81
+ * Optional path base (e.g., `"/pads"` or `"/app/pads"`). If provided with a
82
+ * relative `path` like `"town"`, helpers can compose `"/pads/town"`.
83
+ */
84
+ pathPrefix?: string;
85
+ }
86
+ /**
87
+ * Public manifest for a pad. Hosts (shelves, launchers, routers)
88
+ * read this structure to list, filter, and open pads.
89
+ */
90
+ export interface PadManifest<Meta extends Record<string, unknown> = Record<string, unknown>> {
91
+ /** Unique id (e.g., "town", "value-bridge", "firegate"). */
92
+ id: PadId;
93
+ /** Human title shown in shelves and headers. */
94
+ title: string;
95
+ /** Emoji or icon name (host picks renderer). */
96
+ icon?: string;
97
+ /** Preferred chrome tone. */
98
+ tone?: PadTone;
99
+ /** Search keywords / aliases for launchers. */
100
+ keywords?: string[];
101
+ /** One-line whisper/teaser shown on tiles. */
102
+ whisper?: string;
103
+ /** Routing hints. */
104
+ route?: PadRouteOptions;
105
+ /** Free-form metadata for host-specific extensions. */
106
+ meta?: Meta;
107
+ /** Optional loose tags for filters. */
108
+ tags?: string[];
109
+ /** Minimal scene manifest list (IDs only; UI pads can enrich). */
110
+ scenes?: Array<{
111
+ id: PadSceneId;
112
+ title?: string;
113
+ }>;
114
+ /** Default scene to show when opened (if scenes exist). */
115
+ defaultSceneId?: PadSceneId;
116
+ }
117
+ /** Default hash key used by route helpers. */
118
+ export declare const DEFAULT_HASH_KEY: "pad";
119
+ export interface PadOpenDetail {
120
+ id: PadId;
121
+ /** UX source hint (telemetry / affordance tweaking). */
122
+ via?: 'tile' | 'link' | 'hotkey' | string;
123
+ }
124
+ export interface PadCloseDetail {
125
+ /** If omitted, host may close the currently active pad. */
126
+ id?: PadId;
127
+ reason?: 'esc' | 'route' | 'programmatic' | string;
128
+ }
129
+ export interface PadBulletinUpdatedDetail {
130
+ /** Which pad/topic emitted the bulletin update (if known). */
131
+ padId?: PadId;
132
+ topic?: string;
133
+ }
package/dist/types.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * Garden Pads — shared contracts for pads across apps (Playground, M3 UI, etc).
4
+ *
5
+ * This file contains only **types and constants**. No DOM or framework code.
6
+ * - Pad identity and metadata (PadManifest)
7
+ * - Visual tone and mood vocabulary
8
+ * - Lightweight pad signal contract (for local bus / adapters)
9
+ * - Route preferences (hash/path/query) without assuming a router
10
+ * - Optional CustomEvent detail shapes used by the event helpers
11
+ */
12
+ /** Default route mode for helpers that auto-detect environment. */
13
+ export const DEFAULT_ROUTE_MODE = 'auto';
14
+ /** Default query key used when routing via ?pad=<id>. */
15
+ export const DEFAULT_QUERY_KEY = 'pad';
16
+ /** Default hash key used by route helpers. */
17
+ export const DEFAULT_HASH_KEY = 'pad';
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@gratiaos/pad-core",
3
+ "version": "1.0.0",
4
+ "description": "Shared pad contract, registry, routing, and events for Garden pads.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ },
12
+ "./realtime": {
13
+ "types": "./dist/realtime/index.d.ts",
14
+ "import": "./dist/realtime/index.js"
15
+ }
16
+ },
17
+ "main": "./dist/index.js",
18
+ "module": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "peerDependencies": {
24
+ "react": "^18 || ^19"
25
+ },
26
+ "devDependencies": {
27
+ "@types/react": "^19.1.13",
28
+ "react": "^19.1.1",
29
+ "typescript": "^5.9.3",
30
+ "rimraf": "^6.0.1"
31
+ },
32
+ "scripts": {
33
+ "build": "tsc -b",
34
+ "clean": "rimraf dist"
35
+ }
36
+ }