@omerdlw/base-framework 1.0.0 → 1.0.2

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.
Files changed (171) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +121 -52
  3. package/dist/atoms/index.d.ts +0 -1
  4. package/dist/atoms/index.d.ts.map +1 -1
  5. package/dist/atoms/index.js +1 -64
  6. package/dist/atoms/index.js.map +1 -1
  7. package/dist/error/index.js +22 -25
  8. package/dist/error/index.js.map +1 -1
  9. package/dist/hooks/index.d.ts +2 -9
  10. package/dist/hooks/index.d.ts.map +1 -1
  11. package/dist/hooks/index.js +97 -540
  12. package/dist/hooks/index.js.map +1 -1
  13. package/dist/hooks/types.d.ts +3 -45
  14. package/dist/hooks/types.d.ts.map +1 -1
  15. package/dist/hooks/use-controllable-state.d.ts +1 -1
  16. package/dist/hooks/use-controllable-state.d.ts.map +1 -1
  17. package/dist/hooks/use-global-event.d.ts +0 -2
  18. package/dist/hooks/use-global-event.d.ts.map +1 -1
  19. package/dist/hooks/use-hotkey.d.ts +1 -1
  20. package/dist/hooks/use-hotkey.d.ts.map +1 -1
  21. package/dist/index.js +11283 -11772
  22. package/dist/index.js.map +1 -1
  23. package/dist/kernel/adapters.d.ts.map +1 -1
  24. package/dist/kernel/index.d.ts +1 -0
  25. package/dist/kernel/index.d.ts.map +1 -1
  26. package/dist/kernel/index.js +10 -81
  27. package/dist/kernel/index.js.map +1 -1
  28. package/dist/kernel/installed.d.ts.map +1 -1
  29. package/dist/kernel/page-controller.d.ts.map +1 -1
  30. package/dist/kernel/provider.d.ts.map +1 -1
  31. package/dist/modules/ambient/constants.d.ts +1 -1
  32. package/dist/modules/ambient/constants.d.ts.map +1 -1
  33. package/dist/modules/ambient/index.js +180 -326
  34. package/dist/modules/ambient/index.js.map +1 -1
  35. package/dist/modules/ambient/module.d.ts.map +1 -1
  36. package/dist/modules/background/constants.d.ts +4 -2
  37. package/dist/modules/background/constants.d.ts.map +1 -1
  38. package/dist/modules/background/context.d.ts +3 -0
  39. package/dist/modules/background/context.d.ts.map +1 -1
  40. package/dist/modules/background/hooks.d.ts +1 -1
  41. package/dist/modules/background/index.d.ts +2 -2
  42. package/dist/modules/background/index.d.ts.map +1 -1
  43. package/dist/modules/background/index.js +1172 -1270
  44. package/dist/modules/background/index.js.map +1 -1
  45. package/dist/modules/background/overlay.d.ts +1 -1
  46. package/dist/modules/background/overlay.d.ts.map +1 -1
  47. package/dist/modules/background/types.d.ts +1 -1
  48. package/dist/modules/background/types.d.ts.map +1 -1
  49. package/dist/modules/context-menu/constants.d.ts +1 -1
  50. package/dist/modules/context-menu/constants.d.ts.map +1 -1
  51. package/dist/modules/context-menu/context.d.ts.map +1 -1
  52. package/dist/modules/context-menu/hooks.d.ts.map +1 -1
  53. package/dist/modules/context-menu/index.js +133 -238
  54. package/dist/modules/context-menu/index.js.map +1 -1
  55. package/dist/modules/controls/constants.d.ts +1 -1
  56. package/dist/modules/controls/constants.d.ts.map +1 -1
  57. package/dist/modules/controls/hooks.d.ts +1 -1
  58. package/dist/modules/controls/index.js +28 -162
  59. package/dist/modules/controls/index.js.map +1 -1
  60. package/dist/modules/controls/types.d.ts +1 -1
  61. package/dist/modules/controls/types.d.ts.map +1 -1
  62. package/dist/modules/dock/cards.d.ts +2 -2
  63. package/dist/modules/dock/constants.d.ts +1 -1
  64. package/dist/modules/dock/constants.d.ts.map +1 -1
  65. package/dist/modules/dock/context.d.ts +10 -6
  66. package/dist/modules/dock/context.d.ts.map +1 -1
  67. package/dist/modules/dock/hooks.d.ts +5 -5
  68. package/dist/modules/dock/hooks.d.ts.map +1 -1
  69. package/dist/modules/dock/index.d.ts +1 -1
  70. package/dist/modules/dock/index.d.ts.map +1 -1
  71. package/dist/modules/dock/index.js +2060 -2080
  72. package/dist/modules/dock/index.js.map +1 -1
  73. package/dist/modules/dock/runtime/dock.d.ts.map +1 -1
  74. package/dist/modules/dock/types.d.ts +1 -1
  75. package/dist/modules/dock/types.d.ts.map +1 -1
  76. package/dist/modules/index.js +10789 -11027
  77. package/dist/modules/index.js.map +1 -1
  78. package/dist/modules/loading/constants.d.ts +4 -2
  79. package/dist/modules/loading/constants.d.ts.map +1 -1
  80. package/dist/modules/loading/context.d.ts +3 -0
  81. package/dist/modules/loading/context.d.ts.map +1 -1
  82. package/dist/modules/loading/hooks.d.ts +1 -1
  83. package/dist/modules/loading/hooks.d.ts.map +1 -1
  84. package/dist/modules/loading/index.d.ts +2 -2
  85. package/dist/modules/loading/index.d.ts.map +1 -1
  86. package/dist/modules/loading/index.js +118 -222
  87. package/dist/modules/loading/index.js.map +1 -1
  88. package/dist/modules/loading/types.d.ts +1 -1
  89. package/dist/modules/loading/types.d.ts.map +1 -1
  90. package/dist/modules/media/constants.d.ts +2 -1
  91. package/dist/modules/media/constants.d.ts.map +1 -1
  92. package/dist/modules/media/context.d.ts +3 -0
  93. package/dist/modules/media/context.d.ts.map +1 -1
  94. package/dist/modules/media/index.d.ts +2 -2
  95. package/dist/modules/media/index.d.ts.map +1 -1
  96. package/dist/modules/media/index.js +61 -218
  97. package/dist/modules/media/index.js.map +1 -1
  98. package/dist/modules/modal/constants.d.ts +1 -1
  99. package/dist/modules/modal/constants.d.ts.map +1 -1
  100. package/dist/modules/modal/context.d.ts.map +1 -1
  101. package/dist/modules/modal/hooks.d.ts +2 -2
  102. package/dist/modules/modal/index.js +20 -366
  103. package/dist/modules/modal/index.js.map +1 -1
  104. package/dist/modules/notification/constants.d.ts +4 -2
  105. package/dist/modules/notification/constants.d.ts.map +1 -1
  106. package/dist/modules/notification/context.d.ts +4 -0
  107. package/dist/modules/notification/context.d.ts.map +1 -1
  108. package/dist/modules/notification/hooks.d.ts +1 -1
  109. package/dist/modules/notification/index.d.ts +2 -3
  110. package/dist/modules/notification/index.d.ts.map +1 -1
  111. package/dist/modules/notification/index.js +70 -154
  112. package/dist/modules/notification/index.js.map +1 -1
  113. package/dist/modules/notification/module.d.ts.map +1 -1
  114. package/dist/provider.js +255 -257
  115. package/dist/provider.js.map +1 -1
  116. package/dist/theme.d.ts +1 -0
  117. package/dist/theme.d.ts.map +1 -1
  118. package/dist/theme.js +2 -2
  119. package/dist/theme.js.map +1 -1
  120. package/dist/utils/index.d.ts +5 -6
  121. package/dist/utils/index.d.ts.map +1 -1
  122. package/dist/utils/index.js +1 -57
  123. package/dist/utils/index.js.map +1 -1
  124. package/dist/utils/number.d.ts +0 -1
  125. package/dist/utils/number.d.ts.map +1 -1
  126. package/dist/utils/object.d.ts +0 -2
  127. package/dist/utils/object.d.ts.map +1 -1
  128. package/dist/utils/string.d.ts +0 -3
  129. package/dist/utils/string.d.ts.map +1 -1
  130. package/dist/utils/timing.d.ts +0 -1
  131. package/dist/utils/timing.d.ts.map +1 -1
  132. package/docs/README.md +124 -0
  133. package/docs/architecture-and-rules.md +129 -0
  134. package/docs/core-engine.md +196 -0
  135. package/docs/custom-modules.md +267 -0
  136. package/docs/getting-started.md +199 -0
  137. package/docs/modules/README.md +176 -0
  138. package/docs/modules/ambient.md +150 -0
  139. package/docs/modules/background.md +195 -0
  140. package/docs/modules/context-menu.md +248 -0
  141. package/docs/modules/controls.md +159 -0
  142. package/docs/modules/dock.md +734 -0
  143. package/docs/modules/error-boundary.md +160 -0
  144. package/docs/modules/kernel.md +266 -0
  145. package/docs/modules/loading.md +157 -0
  146. package/docs/modules/media.md +196 -0
  147. package/docs/modules/modal.md +229 -0
  148. package/docs/modules/notification.md +161 -0
  149. package/docs/modules/result.md +135 -0
  150. package/docs/testing.md +160 -0
  151. package/docs/theming.md +185 -0
  152. package/package.json +50 -2
  153. package/templates/AGENTS.md +165 -0
  154. package/dist/hooks/use-async-action.d.ts +0 -3
  155. package/dist/hooks/use-async-action.d.ts.map +0 -1
  156. package/dist/hooks/use-debounce.d.ts +0 -2
  157. package/dist/hooks/use-debounce.d.ts.map +0 -1
  158. package/dist/hooks/use-intersection-observer.d.ts +0 -4
  159. package/dist/hooks/use-intersection-observer.d.ts.map +0 -1
  160. package/dist/hooks/use-media-query.d.ts +0 -2
  161. package/dist/hooks/use-media-query.d.ts.map +0 -1
  162. package/dist/hooks/use-mounted.d.ts +0 -2
  163. package/dist/hooks/use-mounted.d.ts.map +0 -1
  164. package/dist/hooks/use-server-action.d.ts +0 -4
  165. package/dist/hooks/use-server-action.d.ts.map +0 -1
  166. package/dist/hooks/use-storage-state.d.ts +0 -5
  167. package/dist/hooks/use-storage-state.d.ts.map +0 -1
  168. package/dist/modules/theme.d.ts +0 -3
  169. package/dist/modules/theme.d.ts.map +0 -1
  170. package/dist/utils/json.d.ts +0 -3
  171. package/dist/utils/json.d.ts.map +0 -1
@@ -0,0 +1,129 @@
1
+ # Architecture, Boundaries & The 10 Inviolable Rules
2
+
3
+ > **Read this first if you are about to:** contribute to `@omerdlw/base-framework`, author or modify a module, structure downstream application code that consumes the package, or understand how the microkernel enforces layer boundaries.
4
+
5
+ ---
6
+
7
+ ## 1. The Package Architecture Hierarchy
8
+
9
+ `@omerdlw/base-framework` is built as an ultra-lean microkernel orchestrating self-contained declarative UI modules:
10
+
11
+ ```
12
+ @omerdlw/base-framework/modules/* (3. Declarative UI Modules: dock, modal, notification, ambient, background, controls, loading, media, context-menu)
13
+ ↓ imports
14
+ @omerdlw/base-framework/{kernel, provider, theme, error} (2. Orchestration Engine: ModuleHost, RegistryStore, PageController, Error Boundaries)
15
+ ↓ imports
16
+ @omerdlw/base-framework/{atoms, hooks} (1. React Layer: Accessible atoms & universal React hooks)
17
+ ↓ imports
18
+ @omerdlw/base-framework/{utils, tokens, result, events} (0. Pure Foundation: Zero React dependencies, server & edge safe)
19
+ ```
20
+
21
+ ### 1.1 Layer Invariants
22
+
23
+ 1. **Downwards Only:** Higher layers may import from lower layers; a lower layer **never** imports from a higher layer.
24
+ 2. **Server-Safe Foundation:** `@omerdlw/base-framework/utils`, `tokens`, `result`, and `events` have **zero dependencies on React or Next.js DOM**. They can be imported safely in Server Components, Route Handlers, Server Actions, and Edge functions.
25
+ 3. **Module Isolation:** Modules are strict siblings. A module (`dock`, `modal`, etc.) **must never import from another module**. Cross-module communication occurs strictly via declared peers (`uses: ["peerId"]`), `useModuleState()`, or `globalEvents`.
26
+ 4. **No Deep Imports:** Public consumers import only from official subpath exports (`@omerdlw/base-framework/kernel`, `@omerdlw/base-framework/modules/dock`). Direct file-path imports (e.g., `@omerdlw/base-framework/dist/kernel/page.js`) are forbidden.
27
+ 5. **Zero Cycles:** All package internal code maintains 0 cyclical dependencies, verified statically by `npm run check:architecture`.
28
+
29
+ ---
30
+
31
+ ## 2. Canonical Module File Pattern
32
+
33
+ Every first-party module inside `src/modules/<name>/` strictly adheres to a canonical slot pattern:
34
+
35
+ | File | Responsibility | Required? |
36
+ | :--- | :--- | :---: |
37
+ | `index.ts` | Clean barrel export (public API types, hooks, module definition). | ✅ |
38
+ | `types.ts` | Complete TypeScript type contracts and state interfaces (no runtime code). | ✅ |
39
+ | `constants.ts` | Module identifier, theme spec definition (`defineThemeSpec`), constants. | ✅ |
40
+ | `utils.ts` | Pure deterministic helper functions (zero React state). | ✅ |
41
+ | `module.tsx` | Module definition via `defineModule()`, `declare module` type augmentations. | ✅ |
42
+ | `context.tsx` | React Context, Provider, and `useX()` hooks (**at most one context per module**). | Optional |
43
+ | `state.ts` | Initial state factory, pure reducers, state transition functions. | Optional |
44
+ | `hooks.ts` | Component view-models (e.g. `useDockOverlayModel`). | Optional |
45
+ | `overlay.tsx` | Pure visual JSX presentation (consumes view-models; defines no state or refs). | Optional |
46
+ | `motion.ts` | Module-specific animation presets derived from `@omerdlw/base-framework/tokens`. | Optional |
47
+
48
+ ---
49
+
50
+ ## 3. The 10 Inviolable Rules
51
+
52
+ ### Rule 1: Zero Cross-Module Imports
53
+ Modules must remain completely decoupled. Never import from another module directly.
54
+ - ❌ `import { useDock } from "@omerdlw/base-framework/modules/dock"` inside `src/modules/modal/overlay.tsx`
55
+ - ✅ Declare the peer dependency in `module.tsx` (`uses: ["dock"]`) and access state via `useModuleState("dock")` or `definePeer("dock", inert)`.
56
+
57
+ ### Rule 2: Public Subpath Import Contract
58
+ All code imported by consumer applications must resolve through defined subpaths. Deep internal paths are strictly prohibited.
59
+ - ❌ `import { RegistryStore } from "@omerdlw/base-framework/kernel/registry-store"`
60
+ - ✅ `import { usePage, defineModule } from "@omerdlw/base-framework/kernel"`
61
+
62
+ ### Rule 3: Server-Safe Foundation Purity
63
+ The foundation entries (`@omerdlw/base-framework/utils`, `tokens`, `result`, `events`) must never import React, hooks, or browser-specific window globals at module scope.
64
+ - ❌ `import { useState } from "react"` inside `src/utils/`
65
+ - ✅ Keep utility functions pure, deterministic, and testable without a DOM.
66
+
67
+ ### Rule 4: Motion Token Singularity
68
+ Never hardcode inline animation durations (`duration: 0.3`) or custom bezier curves inside UI components. Use universal tokens from `@omerdlw/base-framework/tokens`.
69
+ - ❌ `<motion.div transition={{ duration: 0.35, ease: "easeInOut" }} />`
70
+ - ✅ `<motion.div transition={SPRING_PRESETS.GENTLE} />` or `transition={{ duration: DURATION_TOKENS.FAST, ease: EASING_CURVES.OUT_QUART }}`
71
+
72
+ ### Rule 5: Z-Index Token Contract
73
+ Never use arbitrary stacking classes like `z-50`, `z-[99]`, or `z-[9999]`.
74
+ - ❌ `<div className="fixed inset-0 z-50 bg-black/80">`
75
+ - ✅ `<div className="fixed inset-0 bg-black/80" style={{ zIndex: Z_INDEX.MODAL_BACKDROP }}>`
76
+
77
+ ### Rule 6: Result Pattern for Operations
78
+ Asynchronous workflows, Server Actions, and data mutations must never throw unhandled errors across execution boundaries. Always return `Result<T, E>` using `ok(data)` and `err(message)` from `@omerdlw/base-framework/result`.
79
+ - ❌ `if (!id) throw new Error("Invalid ID");`
80
+ - ✅ `if (!id) return err("Invalid ID"); return ok({ success: true });`
81
+
82
+ ### Rule 7: Declarative Chrome (`usePage`)
83
+ Consumer applications must never handcraft ad-hoc fixed headers, floating toolbars, or sticky navigation bars in individual page views. All route chrome is declared through `usePage()`:
84
+ - ❌ Building custom `<header className="fixed top-0...">` inside every page.
85
+ - ✅ `const page = usePage({ title: "Settings", dock: { description: "User settings", icon: "solar:settings-bold" } });`
86
+
87
+ ### Rule 8: Event Bus Decoupling
88
+ Cross-cutting state notifications, external data updates, or global triggers must pass through `@omerdlw/base-framework/events` (`globalEvents`). Components must not couple directly to external transport implementations.
89
+ - ❌ Hardcoding direct listeners across isolated feature boundaries.
90
+ - ✅ `globalEvents.publish(EVENT_TYPES.STATE_CHANGE, { key, value });` and listen via `useGlobalEvent()`.
91
+
92
+ ### Rule 9: Theme Decoupling
93
+ First-party modules must contain zero locked-in visual styling classes or hardcoded Tailwind colors. All visual design is expressed through theme slots (`defineThemeSpec`) and supplied by the consumer application via `ThemeProvider`.
94
+ - ❌ Hardcoding `bg-neutral-900 border-neutral-800` directly in module overlay markup.
95
+ - ✅ `className={theme.slots.card}` where `theme = useTheme(dockTheme)`.
96
+
97
+ ### Rule 10: Pre-Flight Verification
98
+ Never consider any modification to the package or consumer code complete without running the full verification pipeline:
99
+ ```bash
100
+ npm run type-check && npm run lint && npm run check:architecture && npm test && npm run build
101
+ ```
102
+ All checks must exit with code `0`.
103
+
104
+ ---
105
+
106
+ ## 4. Enforcement Mechanisms
107
+
108
+ | Tool | Command | Scope Enforced |
109
+ | :--- | :--- | :--- |
110
+ | **TypeScript** | `npm run type-check` | Strict compiler type verification across all source files and test suites. |
111
+ | **ESLint** | `npm run lint` | Subpath boundary checks, no deep imports, no `any` without comment, motion rules. |
112
+ | **Architecture Script** | `npm run check:architecture` | Validates 0 import cycles across all files, validates module `uses` declarations. |
113
+ | **Node Test Runner** | `npm test` | 520+ tests asserting kernel lifecycles, module state machines, token correctness, and atom accessibility. |
114
+ | **tsup Bundler** | `npm run build` | Validates tree-shakable ESM packaging and `.d.ts` declaration generation. |
115
+
116
+ ---
117
+
118
+ ## 5. Decision Tree: Where Does Code Go?
119
+
120
+ ```
121
+ What are you adding?
122
+ ├─ Reusable unstyled UI atom (Button, Icon, Spinner, Tooltip) -> src/atoms/
123
+ ├─ First-party UI module (Dock, Modal, Ambient, Toast) -> src/modules/<module>/
124
+ ├─ Universal React hook (useStore, useClickOutside) -> src/hooks/
125
+ ├─ Design token (motion preset, z-index layer) -> src/tokens/
126
+ ├─ Orchestration logic (registry, provider nesting) -> src/kernel/
127
+ ├─ Pure utility or data store (scheduler, store, clamp) -> src/utils/
128
+ └─ Error handling & reporting (reporter, boundary) -> src/error/
129
+ ```
@@ -0,0 +1,196 @@
1
+ # Core Engine — `src/core`
2
+
3
+ > **Read this first if you need to:** know what lives in `src/core`, wire `CoreProvider`, restyle things through themes, use the event bus, motion tokens or z-index layers, or find a utility/hook before writing your own.
4
+ >
5
+ > **Location:** `src/core/` · **Key principle:** frozen upstream, zero imports from higher layers, zero coupling between sibling modules, pure composability. Module and feature specifics live in [modules/](./modules/README.md).
6
+
7
+ ## 1. What is in the core
8
+
9
+ | Entry | Path | Server-safe? | What it is |
10
+ | :------------------ | :------------- | :----------: | :----------------------------------------------------------------------------------------------------------------------------------------- |
11
+ | `@omerdlw/base-framework/utils` | `utils/` | ✅ | `cn`, scheduling, stores, string/number/object helpers, `report`, `toUserMessage`. Bottom of the stack: imports nothing from other layers. |
12
+ | `@omerdlw/base-framework/tokens` | `tokens/` | ✅ | Motion tokens, `Z_INDEX`, shared surface class constants. |
13
+ | `@omerdlw/base-framework/result` | `result.ts` | ✅ | `Result<T, E>`, `createSafeAction`. → [result.md](./modules/result.md) |
14
+ | `@omerdlw/base-framework/events` | `events.ts` | ✅ | `globalEvents` bus, `EVENT_TYPES`, `FrameworkEventMap`. |
15
+ | `@omerdlw/base-framework/hooks` | `hooks/` | client | Universal React hooks. |
16
+ | `@omerdlw/base-framework/theme` | `theme.tsx` | client | Theme specs and provider (shared by core and modules). |
17
+ | `@omerdlw/base-framework/atoms` | `atoms/` | client | Accessible UI atoms (`Button`, `Icon`, `Spinner`, `Tooltip`). |
18
+ | `@omerdlw/base-framework/kernel` | `kernel/` | client | Module host, registry, `usePage`. → [kernel.md](./modules/kernel.md) |
19
+ | `@omerdlw/base-framework/error` | `error/` | client | Boundaries, reporter. → [error-boundary.md](./modules/error-boundary.md) |
20
+ | `@omerdlw/base-framework/provider` | `provider.tsx` | client | `CoreProvider`: the one component that assembles everything. |
21
+
22
+ Server-safe entries (`utils`, `tokens`, `result`, `events`) must not import React or `next/*`, so route handlers, server actions and the Edge can use them.
23
+
24
+ ## 2. `CoreProvider`
25
+
26
+ ```tsx
27
+ // src/app/providers.tsx (simplified)
28
+ <Compose
29
+ providers={[
30
+ AuthProvider,
31
+ AccountProvider,
32
+ [ThemeProvider, { themes }], // from src/config/index.ts
33
+ [
34
+ CoreProvider,
35
+ { modules: defaultModules, registryEntries: APP_REGISTRY_ENTRIES },
36
+ ],
37
+ ]}
38
+ >
39
+ {children}
40
+ </Compose>
41
+ ```
42
+
43
+ | Prop | Type | Meaning |
44
+ | :---------------- | :-------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
45
+ | `modules` | `readonly AnyCoreModule[]` | **Required.** Which modules to install (order does not matter; `uses` sorts them). Listing only some (`[dockModule, notificationModule]`) ships only those. Read **once** at mount. |
46
+ | `registryEntries` | `readonly AppRegistryEntry[]` | Initial (static) registry entries: dock cards, global context menus… `{ type, items: { key: value } }`. |
47
+ | `providers` | `readonly ProviderEntry[]` | Extra providers nested **inside** the module host (so they can use modules). A provider is a component or `[Component, props]`. |
48
+ | `overlays` | `ReactNode` | Rendered after `children`, inside the pipeline. |
49
+ | `slots` | `{ beforeContent?, afterContent?, overlays? }` | Positional content around `children`. |
50
+ | `tooltip` | `boolean \| { delayDuration?, skipDelayDuration? }` | Radix `TooltipProvider` (default on: 300 / 150 ms). `false` removes it (then `Tooltip` components need their own provider). |
51
+
52
+ Resulting tree: `GlobalError` → `TooltipProvider` → `ModuleHost` (registry, page controller stack, module providers, backdrops, **children**, overlays; each module UI inside `ModuleError`) → your `providers` → `beforeContent`, `children`, `afterContent`, `overlays`, `slots.overlays`, `GlobalErrorListener`.
53
+
54
+ `Compose` (from `@omerdlw/base-framework/kernel`) is the helper that nests provider lists; use it for your own pipelines.
55
+
56
+ ## 3. Theme system (`@omerdlw/base-framework/theme`)
57
+
58
+ All visual design of modules is data, not code.
59
+
60
+ ```ts
61
+ // 1. The module declares the contract (slot names)
62
+ export const dockTheme = defineThemeSpec<DockThemeSlot>("dock");
63
+
64
+ // 2. The project supplies the design (src/config/dock.module.theme.ts)
65
+ export const dockThemeConfig = defineTheme(dockTheme, {
66
+ slots: { card: "rounded-[30px] bg-black/60 …", header: "…" }, // Tailwind classes — all slots required
67
+ styles: { card: { zIndex: Z_INDEX.DOCK } }, // optional inline CSS per slot
68
+ });
69
+
70
+ // 3. src/config/index.ts lists it in `themes`; src/app/providers.tsx passes it to <ThemeProvider themes={themes}>
71
+
72
+ // 4. Components read it
73
+ const theme = useTheme(dockTheme); // → { slots, styles }
74
+ <div className={theme.slots.card} style={theme.styles.card} />
75
+ ```
76
+
77
+ | API | Notes |
78
+ | :-------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
79
+ | `defineThemeSpec<Slots>(id)` | Creates the spec object `{ id }`; the generic carries the slot-name union. |
80
+ | `defineTheme(spec, { slots, styles? })` | Type-checked: every slot must be present in `slots`; `styles` is optional and partial. |
81
+ | `ThemeProvider({ themes })` | Builds an `id → config` map. |
82
+ | `useTheme(spec)` | Returns `{ slots, styles }` (empty `styles` default). **Throws** `Theme "<id>" is missing. Add its theme file to src/config/ and list it in src/config/index.ts.` when absent. |
83
+ | `@omerdlw/base-framework/theme` | Re-exports `ThemeProvider`, `defineTheme`, `defineThemeSpec` and `useTheme` (named `useModuleTheme`) for module authors. |
84
+
85
+ Theme files in this repo: `ambient`, `background`, `context-menu`, `controls`, `dock`, `loading`, `modal`, `notification` (module themes), and `error` (core theme). See [theming.md](./theming.md) for full customization details.
86
+
87
+ ## 4. Event bus (`@omerdlw/base-framework/events`)
88
+
89
+ ```ts
90
+ import { globalEvents, EVENT_TYPES } from "@omerdlw/base-framework/events";
91
+
92
+ const off = globalEvents.subscribe(EVENT_TYPES.STATE_CHANGE, (payload) => { … }); // returns unsubscribe
93
+ globalEvents.emit(EVENT_TYPES.STATE_CHANGE, { message: "Saved", notify: true });
94
+ ```
95
+
96
+ | Member | Notes |
97
+ | :--------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------- |
98
+ | `subscribe(event, cb)` | Returns an unsubscribe function. Typed by `FrameworkEventMap`, or `subscribe<T>(name: string, cb)` for ad-hoc names. |
99
+ | `emit(event, payload)` | Calls a **snapshot** of the listeners; a throwing listener is reported (`report`) and does not stop the others. |
100
+ | `emitDebounced(event, payload, waitMs = 100)` / `cancelDebounced(event?)` | Trailing-edge emit per event name. |
101
+ | `unsubscribeAll(event?)`, `hasListeners`, `getListenerCount`, `getAllEvents` | Management. |
102
+ | `EventEmitter<TEvents>` | The class, for isolated buses (tests). |
103
+
104
+ `globalEvents` is a **process-wide singleton** stored on `globalThis` under `Symbol.for("__base_framework_global_events__")`, so duplicated bundles still share one bus.
105
+
106
+ Core events (`EVENT_TYPES`): `API_UNAUTHORIZED { error?, status?, source? }`, `API_ERROR { error, isCritical?, message?, retry?, status? }`, `APP_ERROR { error?, message?, resetError?, source? }`, `STATE_CHANGE { message?, status? }`. Module- and feature-owned events (`DOCK_EVENTS.*`, `AUTH_EVENTS.*`) live with their owner and **augment** `FrameworkEventMap`:
107
+
108
+ ```ts
109
+ declare module "@omerdlw/base-framework/events" {
110
+ interface FrameworkEventMap {
111
+ MY_EVENT: { id: string };
112
+ }
113
+ }
114
+ ```
115
+
116
+ React side: `useGlobalEvent(event | event[] | null, callback, { debounceMs?, throttleMs? })` subscribes for the component's lifetime (callback always fresh; `null` disables); `useEventState(event, initial, reducer)` turns events into state.
117
+
118
+ Who listens to what: the dock turns `APP_ERROR` / critical `API_ERROR` into status cards; the notification module toasts `API_UNAUTHORIZED`, and `APP_ERROR` / `STATE_CHANGE` with `notify: true`.
119
+
120
+ ## 5. Hooks (`@omerdlw/base-framework/hooks`)
121
+
122
+ | Hook | Signature / behaviour |
123
+ | :---------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
124
+ | `useRequiredContext(ctx, hookName, providerName)` | `use(ctx)` or throw `"<hook> must be used within <Provider>"`. |
125
+ | `useStore(store, selector?, isEqual?)` | Subscribe to an `ExternalStore` slice with `useSyncExternalStore`; re-renders only when `isEqual(prev, next)` is false (default `Object.is`). Pass `shallowEqual` for object slices. |
126
+ | `useClickOutside(ref, cb)` | `pointerdown` outside `ref.current` → `cb(event)`. |
127
+ | `useIsomorphicLayoutEffect` | `useLayoutEffect` in the browser, `useEffect` on the server. |
128
+ | `useGlobalEvent(event \| event[] \| null, cb, { debounceMs?, throttleMs? })` | Subscribes to one or multiple global events for the component lifetime with optional debounce or throttle timing. Fresh callback reference maintained without effect restarts. |
129
+ | `useIsFullscreenStateActive()` | Reactively subscribes to global fullscreen state presence (used by `dock` and `loading` to yield when active). |
130
+
131
+ ## 6. Utilities (`@omerdlw/base-framework/utils`)
132
+
133
+ | Group | Exports |
134
+ | :------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
135
+ | Classes | `cn(...)` — `clsx` + `tailwind-merge` (later classes win). |
136
+ | DOM / env | `isBrowser`, `acquireGlobalScrollLock()` (ref-counted; returns a release function that restores previous body and html overflow styles). |
137
+ | Strings | `trimToNull`, `stripTrailingSlash`, `normalizePath` (`""` for empty, `"/"` kept), `isImageIconSource` (http(s) / `/` / `data:image/`). |
138
+ | Numbers | `clamp(val, min, max)`, `toFiniteNumber(value, fallback = 0)`. |
139
+ | Objects | `isObject`, `isPlainObject`, `shallowEqual`, `toArray`. |
140
+ | Timing | `debounce`, `throttle` (both return a function with `.cancel()`). |
141
+ | Reporting | `report(scope, error, level?)`, `setReportSink(fn)` — replaces `console.*` ([error-boundary.md](./modules/error-boundary.md#7-user-facing-messages-and-report)). |
142
+ | User messages | `USER_MESSAGES`, `UserError`, `toUserMessage(error, { fallback?, codes? })`. |
143
+ | Stores | `createStore(initial, { freezeSnapshots = true })` → `{ getSnapshot, subscribe, publish(next \| updater), setState(partial \| updater) }`. `publish` returns `false` when the value is identical (`Object.is`) and **notifies only on change**; subscriber errors are reported, not thrown. In development, snapshots are deep-frozen (disable with `freezeSnapshots: false` for stores holding DOM nodes or class instances). |
144
+ | Scheduling | `createScheduler(options?)` → `{ schedule(cb, delayMs, { label }), scheduleFrame(cb, { label }), cancel(id), cancelAll(), getSnapshot(), subscribe, now }`. Timers and frames are tracked (`pendingCount`, labels), and **inject-able** (`now`, `requestFrame`, `scheduleTimer`, …) so tests run deterministically. The dock uses it instead of ad-hoc `setTimeout`/intervals. |
145
+ | Types | `ExternalStore`, `CreateStoreOptions`, `CoreSchedulerOptions`, `ScheduleHandle`, `ScheduledTaskSnapshot`, `SchedulerSnapshot`, `ReportLevel`, `ReportSink`, `UserMessageOptions`. |
146
+
147
+ ## 7. Motion & design tokens (`@omerdlw/base-framework/tokens`)
148
+
149
+ Rule 4 in practice: **never** write a literal duration/ease in a component.
150
+
151
+ - **GPU compositor only.** Animate `transform` (`translate3d`, `scale`) and `opacity`. Animating layout properties (`height`, `top`, `left`) is forbidden. `gpuTransform(y, scale, x)` builds the canonical `translate3d(...) scale(...)` string; `COMPOSITOR_GPU_STYLE` supplies `backface-visibility`/`will-change` hints.
152
+ - **Durations (`DURATION_TOKENS`, seconds):** `INSTANT` 0.08 · `MICRO` 0.18 · `FAST` 0.28 · `BASE` 0.36 · `MODERATE` 0.44 · `SLOW` 0.58 · `CINEMATIC` 0.86. The same steps exist as `duration-*` utility classes in `globals.css` (`duration-micro` is the default transition duration).
153
+ - **Easings (`EASING_CURVES`):** `OUT_EXPO`, `OUT_QUART` (default CSS easing), `OUT_QUINT`, `IN_OUT_CUBIC`, `IN_CUBIC`, `OUT_BACK` (also `ease-out-expo`, … classes). `MOTION_EASINGS`: `ENTER` (= OUT_QUART), `ENTER_EMPHASIZED` (= OUT_EXPO), `EXIT` (= IN_CUBIC).
154
+ - **Springs (`SPRING_PRESETS`):** `MICRO`, `SNAPPY`, `GENTLE`, `BOUNCY` (`{ type: "spring", stiffness, damping, mass }`).
155
+ - **Tiers (`MOTION_TIERS`):** `MICRO` (4 px), `FAST` (9 px), `STANDARD` (18 px), `SURFACE` (28 px): distance, duration, ease and scale delta per step; dock and modal both use them.
156
+ - **Reduced motion:** `REDUCED_MOTION_TRANSITION` (`INSTANT`, linear). The dock wraps its tree in `MotionConfig reducedMotion="user"` so Framer Motion honours the OS setting; CSS-driven motion uses `motion-safe:` / `motion-reduce:` variants.
157
+ - **Variant builders** (return frozen Framer Motion variants): `contentSwap`, `overlaySurface`, `staggerItem` / `staggerDelay`, `chip`, `press` (`TAP_SCALE_SUBTLE` = 0.97), `dragDismiss`, `contentSwapTransitions`, `surfaceResizeTransition(distancePx)` / `durationForDistance` (longer moves take a bit longer, clamped).
158
+ - **Module motion files** (`modules/*/motion.ts`) compose these into named presets; they are the only place besides `tokens/` where timing literals are tolerated by lint.
159
+
160
+ ### Z-index layers (`Z_INDEX`)
161
+
162
+ Global layers, bottom to top: `BACKGROUND` 0 · `DOCK_BACKDROP` 100 · `DOCK` 110 · `NOTIFICATION` 120 · `MODAL_BACKDROP` 130 · `MODAL` 140 · `SELECT` 150 · `LOADING` 160 · `ERROR_OVERLAY` 170 · `CONTEXT_MENU_BACKDROP` 180 · `CONTEXT_MENU` 190 · `TOOLTIP` 200.
163
+
164
+ Local layers (inside their own stacking context, ≤ 30): `UI_ELEMENT` 10, `DOCK_CARD_BACKGROUND` 0, `DOCK_CARD_CONTENT` 10, `DOCK_CARD_STACK_BASE` 10 (card `i` uses `BASE − i`), `DOCK_CARD_BADGE` 20, `DOCK_CARD_MEDIA` 30, `DOCK_BREADCRUMBS` 10, `DOCK_SURFACE_CONTROL` 10, `DOCK_SURFACE_POPOVER` 30, `DOCK_SURFACE_CENTER` 0, `MODAL_FRAME` 1, `MODAL_STICKY_HEADER` 10.
165
+
166
+ Only the **order** of global layers matters, but each must stay above the local range (and above Tailwind's allowed `z-0/10/20`), otherwise page content covers it. Stacked modals share `Z_INDEX.MODAL` and stack by DOM order. A test asserts `SELECT` and `CONTEXT_MENU` exist and `DROPDOWN` / `DEBUG_OVERLAY` do not.
167
+
168
+ ### Shared class constants
169
+
170
+ `SURFACE_CLASSES` (`surface`, `title`, `description`, `icon`) and `ACTION_TONE_CLASS` give feature surfaces the same glass look as the dock without importing it.
171
+
172
+ ### Global CSS (`src/app/globals.css`)
173
+
174
+ Defines the color variables (`--white`, `--black`, `--primary`) that [ambient](./modules/ambient.md) rewrites, Tailwind v4 `@theme` color bindings (`bg-black`, `text-primary`), the motion token classes (`duration-*`, `ease-*`), and utilities `center` (flex-center) and `skeleton-block` (pulse).
175
+
176
+ ## 8. Module overview
177
+
178
+ | Module | Does | Doc |
179
+ | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------- |
180
+ | `dock` | The framework's only chrome: route card stack, surfaces (sheets/wizards/flows), HUD, operations, status overlays, guards, breadcrumbs, media controls. | [dock.md](./modules/dock.md) |
181
+ | `modal` | Promise-based stacked dialogs and sheets. | [modal.md](./modules/modal.md) |
182
+ | `notification` | Single-slot toasts docked to the dock, event-driven feedback. | [notification.md](./modules/notification.md) |
183
+ | `loading` | Full-screen loading overlay and busy flag. | [loading.md](./modules/loading.md) |
184
+ | `background` | Image / video / YouTube layer behind the page. | [background.md](./modules/background.md) |
185
+ | `media` | The active media session the dock controls operate on. | [media.md](./modules/media.md) |
186
+ | `ambient` | Palette extraction → CSS color variables. | [ambient.md](./modules/ambient.md) |
187
+ | `controls` | Paired control rails beside the dock. | [controls.md](./modules/controls.md) |
188
+ | `context-menu` (`contextMenu`) | Declarative right-click menus. | [context-menu.md](./modules/context-menu.md) |
189
+
190
+ ## 9. Gotchas
191
+
192
+ - Importing `@omerdlw/base-framework/hooks` or `@omerdlw/base-framework/atoms` from a server-safe entry breaks the lint boundary and the Edge bundle.
193
+ - `createStore` snapshots are frozen in development: mutate through `publish`/`setState`, never in place.
194
+ - `useStore` without a selector re-renders on every change; select a slice.
195
+ - A theme file that is not listed in `src/config/index.ts` crashes the first component that uses it (by design, with a clear message).
196
+ - `globalEvents` handlers run synchronously inside `emit`; keep them cheap or schedule work.
@@ -0,0 +1,267 @@
1
+ # Custom Module Authoring Guide — `@omerdlw/base-framework/kernel`
2
+
3
+ > **Read this first if you need to:** create a new declarative module for your application (e.g. an Audio Player, Command Palette, or Interactive Onboarding Tour), hook into `usePage()`, or register custom items in the microkernel's transactional registry.
4
+
5
+ ---
6
+
7
+ ## 1. Overview: The Microkernel Extensibility Model
8
+
9
+ The core strength of `@omerdlw/base-framework` is its **inversion-of-control microkernel**. The kernel knows *no module by name*. All 9 built-in modules (`dock`, `modal`, `notification`, etc.) are authored using the exact same public API available to you: `defineModule()`.
10
+
11
+ When you register a module in `<CoreProvider modules={[myModule, ...]}>`:
12
+ 1. The kernel validates uniqueness of the module's `id`.
13
+ 2. The kernel topologically sorts dependencies declared in `uses: ["peerModule"]` so that providers are nested in guaranteed dependency order.
14
+ 3. The kernel mounts the module's `<Provider>`, renders its `<Backdrop>` behind `{children}`, and renders its `<Overlay>` in an isolated error boundary above `{children}`.
15
+ 4. The kernel wires your module into `usePage()`, allowing any route in your application to declare configuration for your module.
16
+
17
+ ---
18
+
19
+ ## 2. Anatomy of a CoreModule
20
+
21
+ ```ts
22
+ import { defineModule } from "@omerdlw/base-framework/kernel";
23
+
24
+ export const myModule = defineModule({
25
+ id: "myModule", // Unique string identifier
26
+ uses: ["dock"], // Peer dependencies (determines mounting order)
27
+ Provider: MyContextProvider, // Optional: Context provider wrapping application children
28
+ Backdrop: MyBackdropLayer, // Optional: Rendered behind children (e.g. canvas, ambient lighting)
29
+ Overlay: MyOverlayLayer, // Optional: Rendered above children in an error boundary (e.g. sheets, dialogs)
30
+ registry: { // Optional: Registry definition for transactional data
31
+ keyPolicy: "named", // "singleton" | "named" | "path" | "route"
32
+ lifecycle: "route", // "immediate" | "graceful" | "persistent" | "route"
33
+ },
34
+ page: { // Optional: usePage() integration
35
+ select: (config) => config.myModule,
36
+ entries: (slice, context) => [...],
37
+ use: (slice, { set }) => ({ ... }),
38
+ },
39
+ });
40
+ ```
41
+
42
+ ---
43
+
44
+ ## 3. Step-by-Step Tutorial: Building a "Tour Guide" Module
45
+
46
+ Let's create a custom **Onboarding Tour Module** that allows any page to declare step-by-step tooltips via `usePage({ tour: [...] })`.
47
+
48
+ ### Step 1: Define Types & Theme Spec
49
+
50
+ Create `src/modules/tour/types.ts`:
51
+
52
+ ```ts
53
+ export interface TourStep {
54
+ target: string; // CSS selector or data attribute
55
+ title: string;
56
+ description: string;
57
+ }
58
+
59
+ export interface TourConfig {
60
+ steps: TourStep[];
61
+ autoStart?: boolean;
62
+ }
63
+
64
+ export interface TourPageApi {
65
+ start: () => void;
66
+ next: () => void;
67
+ stop: () => void;
68
+ }
69
+ ```
70
+
71
+ Create `src/modules/tour/constants.ts`:
72
+
73
+ ```ts
74
+ import { defineThemeSpec } from "@omerdlw/base-framework/theme";
75
+
76
+ export type TourThemeSlot = "card" | "title" | "description" | "nextButton" | "skipButton";
77
+ export const tourTheme = defineThemeSpec<TourThemeSlot>("tour");
78
+ ```
79
+
80
+ ---
81
+
82
+ ### Step 2: Create the State & Context
83
+
84
+ Create `src/modules/tour/context.tsx`:
85
+
86
+ ```tsx
87
+ "use client";
88
+
89
+ import React, { createContext, useContext, useState, useMemo } from "react";
90
+ import type { TourStep } from "./types";
91
+
92
+ interface TourContextValue {
93
+ currentStepIndex: number;
94
+ currentStep: TourStep | null;
95
+ isActive: boolean;
96
+ start: () => void;
97
+ next: () => void;
98
+ stop: () => void;
99
+ }
100
+
101
+ const TourContext = createContext<TourContextValue | null>(null);
102
+
103
+ export function TourProvider({ children }: { children?: React.ReactNode }) {
104
+ const [steps, setSteps] = useState<TourStep[]>([]);
105
+ const [index, setIndex] = useState(0);
106
+ const [isActive, setIsActive] = useState(false);
107
+
108
+ const value = useMemo<TourContextValue>(() => ({
109
+ currentStepIndex: index,
110
+ currentStep: isActive ? steps[index] ?? null : null,
111
+ isActive,
112
+ start: () => { setIndex(0); setIsActive(true); },
113
+ next: () => {
114
+ if (index + 1 < steps.length) setIndex(index + 1);
115
+ else setIsActive(false);
116
+ },
117
+ stop: () => setIsActive(false),
118
+ }), [index, isActive, steps]);
119
+
120
+ return <TourContext.Provider value={value}>{children}</TourContext.Provider>;
121
+ }
122
+
123
+ export function useTour() {
124
+ const ctx = useContext(TourContext);
125
+ if (!ctx) throw new Error("useTour must be used within TourProvider");
126
+ return ctx;
127
+ }
128
+ ```
129
+
130
+ ---
131
+
132
+ ### Step 3: Create the Visual Overlay
133
+
134
+ Create `src/modules/tour/overlay.tsx`:
135
+
136
+ ```tsx
137
+ "use client";
138
+
139
+ import React from "react";
140
+ import { useTheme } from "@omerdlw/base-framework/theme";
141
+ import { Button } from "@omerdlw/base-framework/atoms";
142
+ import { useTour } from "./context";
143
+ import { tourTheme } from "./constants";
144
+
145
+ export function TourOverlay() {
146
+ const { currentStep, isActive, next, stop } = useTour();
147
+ const theme = useTheme(tourTheme);
148
+
149
+ if (!isActive || !currentStep) return null;
150
+
151
+ return (
152
+ <div className="fixed inset-0 pointer-events-none z-50 flex items-center justify-center">
153
+ <div className={`${theme.slots.card} pointer-events-auto`}>
154
+ <h3 className={theme.slots.title}>{currentStep.title}</h3>
155
+ <p className={theme.slots.description}>{currentStep.description}</p>
156
+ <div className="mt-4 flex gap-2">
157
+ <Button onClick={stop} className={theme.slots.skipButton}>Skip</Button>
158
+ <Button onClick={next} className={theme.slots.nextButton}>Next</Button>
159
+ </div>
160
+ </div>
161
+ </div>
162
+ );
163
+ }
164
+ ```
165
+
166
+ ---
167
+
168
+ ### Step 4: Define the Module & Wire into `usePage`
169
+
170
+ Create `src/modules/tour/module.tsx`:
171
+
172
+ ```tsx
173
+ "use client";
174
+
175
+ import { defineModule } from "@omerdlw/base-framework/kernel";
176
+ import { TourProvider, useTour } from "./context";
177
+ import { TourOverlay } from "./overlay";
178
+ import type { TourConfig, TourPageApi } from "./types";
179
+
180
+ export const tourModule = defineModule<
181
+ "tour",
182
+ null,
183
+ TourConfig,
184
+ TourPageApi
185
+ >({
186
+ id: "tour",
187
+ Provider: TourProvider,
188
+ Overlay: TourOverlay,
189
+ page: {
190
+ select: (config) => config.tour,
191
+ use: (slice, { set }) => {
192
+ const { start, next, stop } = useTour();
193
+ return { start, next, stop };
194
+ },
195
+ },
196
+ });
197
+
198
+ // TypeScript type augmentation for auto-complete in usePage()
199
+ declare module "@omerdlw/base-framework/kernel" {
200
+ interface PageConfig {
201
+ tour?: TourConfig;
202
+ }
203
+ }
204
+ ```
205
+
206
+ Create `src/modules/tour/index.ts`:
207
+
208
+ ```ts
209
+ export * from "./types";
210
+ export * from "./constants";
211
+ export * from "./context";
212
+ export * from "./module";
213
+ ```
214
+
215
+ ---
216
+
217
+ ## 4. Consuming Your Custom Module
218
+
219
+ ### 1. Mount in `<CoreProvider>`:
220
+
221
+ In `src/app/providers.tsx`:
222
+
223
+ ```tsx
224
+ import { CoreProvider } from "@omerdlw/base-framework/provider";
225
+ import { dockModule } from "@omerdlw/base-framework/modules/dock";
226
+ import { tourModule } from "@/modules/tour";
227
+
228
+ export function Providers({ children }: { children: React.ReactNode }) {
229
+ return (
230
+ <CoreProvider modules={[dockModule, tourModule]}>
231
+ {children}
232
+ </CoreProvider>
233
+ );
234
+ }
235
+ ```
236
+
237
+ ### 2. Declare in Any Page Route:
238
+
239
+ In `src/app/dashboard/page.tsx`:
240
+
241
+ ```tsx
242
+ "use client";
243
+
244
+ import { usePage } from "@omerdlw/base-framework/kernel";
245
+
246
+ export default function DashboardPage() {
247
+ const page = usePage({
248
+ title: "Dashboard",
249
+ tour: {
250
+ steps: [
251
+ { target: "#metrics", title: "Key Metrics", description: "Monitor your KPIs here in real time." },
252
+ { target: "#export", title: "Export Data", description: "Download CSV reports with one click." },
253
+ ],
254
+ },
255
+ });
256
+
257
+ return (
258
+ <div>
259
+ <button onClick={() => page.modules.tour?.start()}>
260
+ Take a Tour
261
+ </button>
262
+ </div>
263
+ );
264
+ }
265
+ ```
266
+
267
+ Notice that `page.modules.tour` is **fully type-safe** and auto-completed by TypeScript!