@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.
- package/LICENSE +1 -1
- package/README.md +121 -52
- package/dist/atoms/index.d.ts +0 -1
- package/dist/atoms/index.d.ts.map +1 -1
- package/dist/atoms/index.js +1 -64
- package/dist/atoms/index.js.map +1 -1
- package/dist/error/index.js +22 -25
- package/dist/error/index.js.map +1 -1
- package/dist/hooks/index.d.ts +2 -9
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +97 -540
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/types.d.ts +3 -45
- package/dist/hooks/types.d.ts.map +1 -1
- package/dist/hooks/use-controllable-state.d.ts +1 -1
- package/dist/hooks/use-controllable-state.d.ts.map +1 -1
- package/dist/hooks/use-global-event.d.ts +0 -2
- package/dist/hooks/use-global-event.d.ts.map +1 -1
- package/dist/hooks/use-hotkey.d.ts +1 -1
- package/dist/hooks/use-hotkey.d.ts.map +1 -1
- package/dist/index.js +11283 -11772
- package/dist/index.js.map +1 -1
- package/dist/kernel/adapters.d.ts.map +1 -1
- package/dist/kernel/index.d.ts +1 -0
- package/dist/kernel/index.d.ts.map +1 -1
- package/dist/kernel/index.js +10 -81
- package/dist/kernel/index.js.map +1 -1
- package/dist/kernel/installed.d.ts.map +1 -1
- package/dist/kernel/page-controller.d.ts.map +1 -1
- package/dist/kernel/provider.d.ts.map +1 -1
- package/dist/modules/ambient/constants.d.ts +1 -1
- package/dist/modules/ambient/constants.d.ts.map +1 -1
- package/dist/modules/ambient/index.js +180 -326
- package/dist/modules/ambient/index.js.map +1 -1
- package/dist/modules/ambient/module.d.ts.map +1 -1
- package/dist/modules/background/constants.d.ts +4 -2
- package/dist/modules/background/constants.d.ts.map +1 -1
- package/dist/modules/background/context.d.ts +3 -0
- package/dist/modules/background/context.d.ts.map +1 -1
- package/dist/modules/background/hooks.d.ts +1 -1
- package/dist/modules/background/index.d.ts +2 -2
- package/dist/modules/background/index.d.ts.map +1 -1
- package/dist/modules/background/index.js +1172 -1270
- package/dist/modules/background/index.js.map +1 -1
- package/dist/modules/background/overlay.d.ts +1 -1
- package/dist/modules/background/overlay.d.ts.map +1 -1
- package/dist/modules/background/types.d.ts +1 -1
- package/dist/modules/background/types.d.ts.map +1 -1
- package/dist/modules/context-menu/constants.d.ts +1 -1
- package/dist/modules/context-menu/constants.d.ts.map +1 -1
- package/dist/modules/context-menu/context.d.ts.map +1 -1
- package/dist/modules/context-menu/hooks.d.ts.map +1 -1
- package/dist/modules/context-menu/index.js +133 -238
- package/dist/modules/context-menu/index.js.map +1 -1
- package/dist/modules/controls/constants.d.ts +1 -1
- package/dist/modules/controls/constants.d.ts.map +1 -1
- package/dist/modules/controls/hooks.d.ts +1 -1
- package/dist/modules/controls/index.js +28 -162
- package/dist/modules/controls/index.js.map +1 -1
- package/dist/modules/controls/types.d.ts +1 -1
- package/dist/modules/controls/types.d.ts.map +1 -1
- package/dist/modules/dock/cards.d.ts +2 -2
- package/dist/modules/dock/constants.d.ts +1 -1
- package/dist/modules/dock/constants.d.ts.map +1 -1
- package/dist/modules/dock/context.d.ts +10 -6
- package/dist/modules/dock/context.d.ts.map +1 -1
- package/dist/modules/dock/hooks.d.ts +5 -5
- package/dist/modules/dock/hooks.d.ts.map +1 -1
- package/dist/modules/dock/index.d.ts +1 -1
- package/dist/modules/dock/index.d.ts.map +1 -1
- package/dist/modules/dock/index.js +2060 -2080
- package/dist/modules/dock/index.js.map +1 -1
- package/dist/modules/dock/runtime/dock.d.ts.map +1 -1
- package/dist/modules/dock/types.d.ts +1 -1
- package/dist/modules/dock/types.d.ts.map +1 -1
- package/dist/modules/index.js +10789 -11027
- package/dist/modules/index.js.map +1 -1
- package/dist/modules/loading/constants.d.ts +4 -2
- package/dist/modules/loading/constants.d.ts.map +1 -1
- package/dist/modules/loading/context.d.ts +3 -0
- package/dist/modules/loading/context.d.ts.map +1 -1
- package/dist/modules/loading/hooks.d.ts +1 -1
- package/dist/modules/loading/hooks.d.ts.map +1 -1
- package/dist/modules/loading/index.d.ts +2 -2
- package/dist/modules/loading/index.d.ts.map +1 -1
- package/dist/modules/loading/index.js +118 -222
- package/dist/modules/loading/index.js.map +1 -1
- package/dist/modules/loading/types.d.ts +1 -1
- package/dist/modules/loading/types.d.ts.map +1 -1
- package/dist/modules/media/constants.d.ts +2 -1
- package/dist/modules/media/constants.d.ts.map +1 -1
- package/dist/modules/media/context.d.ts +3 -0
- package/dist/modules/media/context.d.ts.map +1 -1
- package/dist/modules/media/index.d.ts +2 -2
- package/dist/modules/media/index.d.ts.map +1 -1
- package/dist/modules/media/index.js +61 -218
- package/dist/modules/media/index.js.map +1 -1
- package/dist/modules/modal/constants.d.ts +1 -1
- package/dist/modules/modal/constants.d.ts.map +1 -1
- package/dist/modules/modal/context.d.ts.map +1 -1
- package/dist/modules/modal/hooks.d.ts +2 -2
- package/dist/modules/modal/index.js +20 -366
- package/dist/modules/modal/index.js.map +1 -1
- package/dist/modules/notification/constants.d.ts +4 -2
- package/dist/modules/notification/constants.d.ts.map +1 -1
- package/dist/modules/notification/context.d.ts +4 -0
- package/dist/modules/notification/context.d.ts.map +1 -1
- package/dist/modules/notification/hooks.d.ts +1 -1
- package/dist/modules/notification/index.d.ts +2 -3
- package/dist/modules/notification/index.d.ts.map +1 -1
- package/dist/modules/notification/index.js +70 -154
- package/dist/modules/notification/index.js.map +1 -1
- package/dist/modules/notification/module.d.ts.map +1 -1
- package/dist/provider.js +255 -257
- package/dist/provider.js.map +1 -1
- package/dist/theme.d.ts +1 -0
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +2 -2
- package/dist/theme.js.map +1 -1
- package/dist/utils/index.d.ts +5 -6
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -57
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/number.d.ts +0 -1
- package/dist/utils/number.d.ts.map +1 -1
- package/dist/utils/object.d.ts +0 -2
- package/dist/utils/object.d.ts.map +1 -1
- package/dist/utils/string.d.ts +0 -3
- package/dist/utils/string.d.ts.map +1 -1
- package/dist/utils/timing.d.ts +0 -1
- package/dist/utils/timing.d.ts.map +1 -1
- package/docs/README.md +124 -0
- package/docs/architecture-and-rules.md +129 -0
- package/docs/core-engine.md +196 -0
- package/docs/custom-modules.md +267 -0
- package/docs/getting-started.md +199 -0
- package/docs/modules/README.md +176 -0
- package/docs/modules/ambient.md +150 -0
- package/docs/modules/background.md +195 -0
- package/docs/modules/context-menu.md +248 -0
- package/docs/modules/controls.md +159 -0
- package/docs/modules/dock.md +734 -0
- package/docs/modules/error-boundary.md +160 -0
- package/docs/modules/kernel.md +266 -0
- package/docs/modules/loading.md +157 -0
- package/docs/modules/media.md +196 -0
- package/docs/modules/modal.md +229 -0
- package/docs/modules/notification.md +161 -0
- package/docs/modules/result.md +135 -0
- package/docs/testing.md +160 -0
- package/docs/theming.md +185 -0
- package/package.json +50 -2
- package/templates/AGENTS.md +165 -0
- package/dist/hooks/use-async-action.d.ts +0 -3
- package/dist/hooks/use-async-action.d.ts.map +0 -1
- package/dist/hooks/use-debounce.d.ts +0 -2
- package/dist/hooks/use-debounce.d.ts.map +0 -1
- package/dist/hooks/use-intersection-observer.d.ts +0 -4
- package/dist/hooks/use-intersection-observer.d.ts.map +0 -1
- package/dist/hooks/use-media-query.d.ts +0 -2
- package/dist/hooks/use-media-query.d.ts.map +0 -1
- package/dist/hooks/use-mounted.d.ts +0 -2
- package/dist/hooks/use-mounted.d.ts.map +0 -1
- package/dist/hooks/use-server-action.d.ts +0 -4
- package/dist/hooks/use-server-action.d.ts.map +0 -1
- package/dist/hooks/use-storage-state.d.ts +0 -5
- package/dist/hooks/use-storage-state.d.ts.map +0 -1
- package/dist/modules/theme.d.ts +0 -3
- package/dist/modules/theme.d.ts.map +0 -1
- package/dist/utils/json.d.ts +0 -3
- package/dist/utils/json.d.ts.map +0 -1
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
# Dock — `@omerdlw/base-framework/modules/dock`
|
|
2
|
+
|
|
3
|
+
> **Read this first if you need to:** show a page title/icon/banner, open a sheet or wizard, block navigation, show progress or an error, add toolbar buttons, or understand why the bottom card does what it does.
|
|
4
|
+
|
|
5
|
+
## 1. At a glance
|
|
6
|
+
|
|
7
|
+
| | |
|
|
8
|
+
| :----------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
9
|
+
| **Import** | `import { … } from "@omerdlw/base-framework/modules/dock"` (barrel only, no deep imports from `src/app` or `src/features`) |
|
|
10
|
+
| **Module id** | `dock` (`dockModule`, installed by `CoreProvider` through `defaultModules`) |
|
|
11
|
+
| **Renders** | `Dock` overlay, portalled to `document.body`, DOM id `dock-card-stack` |
|
|
12
|
+
| **Registry type** | `dock` (key policy `path`, lifecycle `route`, cleanup delay 600 ms, entries merged by priority) |
|
|
13
|
+
| **Page slice** | `usePage({ dock: {…} })` → controller `page.modules.dock` |
|
|
14
|
+
| **Peers (`uses`)** | `loading`, `media`, `notification` (optional; inert stand-ins when absent) |
|
|
15
|
+
| **Theme** | `dockTheme` slots in `src/config/dock.module.theme.ts` |
|
|
16
|
+
| **Events it owns** | `DOCK_STATUS_SET`, `DOCK_STATUS_CLEAR`, `DOCK_GUARD`, `DOCK_NOT_FOUND`, `DOCK_NAVIGATE_START` / `DOCK_NAVIGATE` / `DOCK_NAVIGATE_END`, `DOCK_UPDATE_BADGE` |
|
|
17
|
+
| **CSS output** | `--dock-h` on `<html>` (dock height + 16 px buffer) |
|
|
18
|
+
| **Tests** | `tests/modules/dock.test.ts` |
|
|
19
|
+
|
|
20
|
+
## 2. Mental model
|
|
21
|
+
|
|
22
|
+
The framework has **no header or nav bar** ([Rule 7](../architecture-and-rules.md#rule-7-declarative-chrome)). Every page is a **card** in a floating stack at the bottom of the screen.
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
┌────────────────────────────┐ ← backdrop (expanded / overlay only)
|
|
26
|
+
│ breadcrumbs / toast card │ ← "companion" card above the stack
|
|
27
|
+
├────────────────────────────┤
|
|
28
|
+
│ TOP CARD (what you see) │ ← route card, surface, status, HUD …
|
|
29
|
+
│ ghost card behind it │ ← other registered pages (collapsed)
|
|
30
|
+
│ ghost card behind that │
|
|
31
|
+
└────────────────────────────┘
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Three ideas explain everything else:
|
|
35
|
+
|
|
36
|
+
1. **Cards come from the registry.** A page registers a card (`usePage({ dock })`, `useDockRegistration`, or static entries in `src/app/registry.tsx`). The card whose `path` matches the URL is the active item. Expanding the stack lists all registered cards and turns it into a navigator.
|
|
37
|
+
2. **One thing owns the top card at a time ("attention").** Surface, status, HUD, operation, loading and the plain route all compete; the highest priority wins (see [§7](#7-attention-who-owns-the-top-card)).
|
|
38
|
+
3. **Navigation goes through the dock.** `navigate()` runs guards, records scroll/focus so "back" restores position, tracks a transaction (timeout, supersession), and prefetches on hover intent.
|
|
39
|
+
|
|
40
|
+
## 3. Quick start
|
|
41
|
+
|
|
42
|
+
### 3.1 Give a page its card
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
"use client";
|
|
46
|
+
import { usePage } from "@omerdlw/base-framework/kernel";
|
|
47
|
+
|
|
48
|
+
export function SettingsClient() {
|
|
49
|
+
usePage({
|
|
50
|
+
title: "Settings", // becomes dock.title unless dock.title is set
|
|
51
|
+
dock: {
|
|
52
|
+
description: "Account & privacy",
|
|
53
|
+
icon: "solar:settings-bold",
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
return <main>…</main>;
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`usePage` registers the card at the current pathname and removes it when the route goes away (lifecycle `route`). Static cards that exist on every page (Home, Account) live in `src/app/registry.tsx`:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
export const APP_REGISTRY_ENTRIES = [
|
|
64
|
+
{
|
|
65
|
+
type: "dock",
|
|
66
|
+
items: {
|
|
67
|
+
"/": {
|
|
68
|
+
title: "Home",
|
|
69
|
+
icon: "solar:home-2-bold",
|
|
70
|
+
name: "home",
|
|
71
|
+
path: "/",
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 3.2 Open a surface (sheet) and wait for the result
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
const { openSurface } = useDockActions();
|
|
82
|
+
|
|
83
|
+
const result = await openSurface({
|
|
84
|
+
component: InviteForm,
|
|
85
|
+
props: { teamId },
|
|
86
|
+
title: "Invite teammate",
|
|
87
|
+
icon: "solar:user-plus-bold",
|
|
88
|
+
});
|
|
89
|
+
if (result?.success) toast("Invite sent");
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The promise resolves when the surface closes. Close it from inside with `close({ success: true, data })`.
|
|
93
|
+
|
|
94
|
+
### 3.3 Block navigation while a form is dirty
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
usePage({
|
|
98
|
+
dock: {
|
|
99
|
+
title: "Edit profile",
|
|
100
|
+
guard: { when: isDirty, message: "Discard your changes?" },
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
// or, from any component:
|
|
104
|
+
useDockGuard({ when: isDirty, message: "Discard your changes?" });
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 3.4 Report progress
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
const { operations } = useDockActions();
|
|
111
|
+
const op = operations.start({ label: "Uploading", progress: 0 });
|
|
112
|
+
operations.update(op!.id, { progress: 0.5 });
|
|
113
|
+
operations.complete(op!.id);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 3.5 Show an error / toast-like status from anywhere (even outside React)
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { globalEvents } from "@omerdlw/base-framework/events";
|
|
120
|
+
import { DOCK_EVENTS } from "@omerdlw/base-framework/modules/dock";
|
|
121
|
+
|
|
122
|
+
globalEvents.emit(DOCK_EVENTS.STATUS_SET, {
|
|
123
|
+
type: "SAVED",
|
|
124
|
+
title: "Saved",
|
|
125
|
+
description: "Profile updated",
|
|
126
|
+
icon: "solar:check-circle-bold",
|
|
127
|
+
duration: 2500,
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## 4. Capability matrix
|
|
132
|
+
|
|
133
|
+
| Capability | What you get | API | Section |
|
|
134
|
+
| :-------------- | :------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------- | :-------------------------------------------- |
|
|
135
|
+
| Route card | Title, description, icon, banner, badge, icon overlay, per-state styles, custom width, custom body | `usePage({ dock })`, `useDockRegistration`, `useDockConfig`, `useDockBanner`, registry entries | [§6](#6-route-cards-and-the-page-slice) |
|
|
136
|
+
| Toolbar actions | Buttons on the card (order, tone, badge, tooltip, hide) | `dock.actions`, `useDockContextActions`, `defineDockAction` | [§10](#10-contextual-actions) |
|
|
137
|
+
| Surfaces | The card grows into a sheet; promise result; stack; drag/swipe/Esc dismissal; focus trap & restore | `openSurface`, `defineSurface`, `useSurface`, `dock.surfaces` | [§8](#8-surfaces) |
|
|
138
|
+
| Steps | Multi-step wizard inside one surface | `defineStepSurface`, `useSurfaceStep`, `pushStep` / `popStep` / `goToStep` | [§8.5](#85-steps-wizards) |
|
|
139
|
+
| Flows | URL-restorable, promise-based multi-screen flow with snapshot and return handshake | `useSurfaceFlow`, `openSurfaceFlow`, `useSurfaceReturn` | [§8.6](#86-flows) |
|
|
140
|
+
| HUD | Transient heads-up card (selection mode, undo, progress) | `defineHud`, `useHud`, `useDockHud` | [§9](#9-hud) |
|
|
141
|
+
| Operations | Long-running task with progress, cancel and a HUD | `operations.*` | [§9.4](#94-operations) |
|
|
142
|
+
| Status overlays | Errors, offline, 404, guard confirmation, custom statuses; persisted across reloads | `DOCK_EVENTS.STATUS_SET`, `APP_ERROR`, `API_ERROR`, `createErrorStatus`… | [§11](#11-status-overlays) |
|
|
143
|
+
| Guards | Block in-app navigation and tab close | `useDockGuard`, `dock.guard`, `registerGuard` | [§12](#12-navigation-guards-and-transactions) |
|
|
144
|
+
| Navigation | Guarded `navigate`, transactions, prefetch on intent, route policy | `useDockActions().navigate` | [§12](#12-navigation-guards-and-transactions) |
|
|
145
|
+
| Continuity | Scroll/focus snapshots per path, surface return handoff | `continuity.*`, `useSurfaceReturn` | [§13](#13-continuity-and-return-handoffs) |
|
|
146
|
+
| Breadcrumbs | Auto-generated from the URL, overridable | `defineBreadcrumb`, `BreadcrumbConfig` | [§14](#14-breadcrumbs) |
|
|
147
|
+
| Media | Play/pause icon, controls (seek, ±10 s, volume, loop, speed, PiP) while a media session exists | `media` module | [§15](#15-media-integration) |
|
|
148
|
+
| Geometry | Live height/width, `--dock-h`, `useDockHeight` | `useDockHeight`, `useDockDimensions` | [§16](#16-layout-and-geometry) |
|
|
149
|
+
| Theming | 137 style slots, zero hard-coded classes | `dockTheme` | [§17](#17-theming) |
|
|
150
|
+
|
|
151
|
+
## 5. Public API reference
|
|
152
|
+
|
|
153
|
+
Everything below is exported from `@omerdlw/base-framework/modules/dock`. Anything not listed is internal (see [§19](#19-file-map)).
|
|
154
|
+
|
|
155
|
+
### 5.1 Installation and state
|
|
156
|
+
|
|
157
|
+
| Export | Kind | Notes |
|
|
158
|
+
| :----------------------------------------------- | :-------- | :---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
159
|
+
| `dockModule` | module | `defineModule` definition; list it in `CoreProvider modules`. |
|
|
160
|
+
| `Dock` | component | The overlay. Mounted by the module host, not by you. |
|
|
161
|
+
| `DockProvider` | component | Owns the state store, actions, guards registry, scheduler. Props: `breadcrumbConfig`, `mediaAction`, `notFoundAction`, `scheduler` (see below). |
|
|
162
|
+
| `useDockActions()` | hook | **Stable** `DockActions` object (never re-renders on state change). Needs `DockProvider`, otherwise throws. |
|
|
163
|
+
| `useDockState()` | hook | Whole `DockState` (re-renders on every change; prefer a selector). |
|
|
164
|
+
| `useDockSelector(selector, isEqual = Object.is)` | hook | Memoized slice of `DockState`. |
|
|
165
|
+
| `useDock()` | hook | The **view controller** (items, expansion, hover, guarded navigate, transactions). Built once, by the dock itself. Do not call it in pages. |
|
|
166
|
+
| `useDockHeight()` | hook | `{ dockHeight, padding: { paddingBottom } }`. Use `padding` so content is never hidden behind the dock. |
|
|
167
|
+
| `useDockDimensions()` | hook | `{ height, width, isSurface }` of the active card. |
|
|
168
|
+
|
|
169
|
+
`DockProvider` props (only needed if you wrap or replace the provider in your own module definition; `defaultModules` passes none):
|
|
170
|
+
|
|
171
|
+
| Prop | Type | Effect |
|
|
172
|
+
| :----------------- | :----------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
173
|
+
| `breadcrumbConfig` | `BreadcrumbConfig` | Root crumb, `resolveSegment`, `resolvePath` (see [§14](#14-breadcrumbs)). |
|
|
174
|
+
| `mediaAction` | `ComponentType` | Extra component stacked next to the card's inline action while media plays. The built-in media controls already take the top card's action slot (see [§15](#15-media-integration)). |
|
|
175
|
+
| `notFoundAction` | `ComponentType` | Rendered as the action of the 404 status (for example a "Back home" button). |
|
|
176
|
+
| `scheduler` | `DockScheduler` | Inject a deterministic scheduler (tests). Default: `createDockScheduler()`. |
|
|
177
|
+
|
|
178
|
+
### 5.2 Registering cards
|
|
179
|
+
|
|
180
|
+
| Export | Signature | Notes |
|
|
181
|
+
| :---------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
182
|
+
| `usePage({ dock })` | from `@omerdlw/base-framework/kernel` | Preferred. See [§6](#6-route-cards-and-the-page-slice). |
|
|
183
|
+
| `useDockRegistration(config, options?)` | `(DockPageConfig \| null, RegistryMetadata & { enabled? })` | Register a card outside `usePage` (for example a layout-level account card). `options.priority` / `source` decide who wins on the same path. |
|
|
184
|
+
| `useDockBanner(banner, options?)` | `(string \| DockBannerInput \| null, { path?, position?, size?, opacity?, repeat? })` | Register only a banner image for a path. |
|
|
185
|
+
| `useDockConfig(configOrTitle?, options?)` | returns `{ activeItem, expanded, dockHeight, pathname, navigate, openSurface, closeSurface, closeAllSurfaces, setHud, clearHud, setExpanded, setSearchQuery }` | Legacy convenience: registers a card **and** returns a small action bag. `useDockConfig("Title")` is shorthand for `{ title }`. |
|
|
186
|
+
|
|
187
|
+
### 5.3 Surfaces
|
|
188
|
+
|
|
189
|
+
| Export | Kind | Notes |
|
|
190
|
+
| :------------------------------------------------------------------------------------- | :-------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
191
|
+
| `defineSurface(def)` | builder | Returns a factory `(props, overrides) → SurfaceDescriptor` plus `.open(openSurface, props)`, `.use()`, `.config`, `.id`, `.component`. |
|
|
192
|
+
| `useSurface(defOrId?)` | hook | With a definition: `[open, { close, closeAll, isOpen, surfaceStack }]` (also as properties `open`, `close`, …). Without: `{ openSurface, closeSurface, closeAllSurfaces, surfaceStack }`. |
|
|
193
|
+
| `defineStepSurface(def)` | builder | Wizard factory; `steps` may be a function of props. |
|
|
194
|
+
| `useSurfaceStep()` | hook | `{ currentStep, steps, stepIndex, stepsTotal, isFirst, isLast, next, prev, goTo, push, close }` for the active surface. |
|
|
195
|
+
| `createSurfaceFlowBuilder`, `createSurfaceEntryDefinition`, `createInlineSurfaceEntry` | factories | Lower-level entry factories (what `defineSurface` is built on). |
|
|
196
|
+
| `useSurfaceFlow(flowDef)` | hook | `{ open, snapshot, update, complete, cancel, isOpen, activeFlow, flowId }`; auto-restores from history. |
|
|
197
|
+
| `useSurfaceReturn()` | hook | `{ entries, peek(), consume(id?) }` — handoffs delivered to the **current path**. |
|
|
198
|
+
| `DockSurfaceAction` | component | `<DockSurfaceAction>{node}</DockSurfaceAction>` sets the surface's bottom action slot. |
|
|
199
|
+
| `DockSurfaceExtension` | component | Shelf item below the surface. Props: `id`, `align` (`left` \| `center` \| `right`), `order`, `className`, `unstyled`, `children`. |
|
|
200
|
+
| `DockSurfaceHeaderButton` | component | Button in the surface header. Props: `icon`, `onClick`, `disabled`, `ariaLabel`, `className`, `children` (text variant). |
|
|
201
|
+
| `useSurfaceHeader()` | hook | Returns `(patch) ⇒ void`; `patch.headerAction` sets the header action node. |
|
|
202
|
+
| `useSurfaceAction(node)` | hook | Same as `DockSurfaceAction`, as a hook. |
|
|
203
|
+
| `useSurfaceId()`, `useSurfaceDimensions()` | hooks | Id and `{ width }` of the surface you render in. |
|
|
204
|
+
|
|
205
|
+
### 5.4 HUD, actions, breadcrumbs, guards, status
|
|
206
|
+
|
|
207
|
+
| Export | Kind | Notes |
|
|
208
|
+
| :----------------------------------------------------------------------------- | :--------------------- | :----------------------------------------------------------- |
|
|
209
|
+
| `defineHud(def)`, `useHud(def?)`, `useDockHud(descriptor)` | builder / hooks | See [§9](#9-hud). |
|
|
210
|
+
| `defineDockAction(def)`, `useDockContextActions(actions)` | builder / hook | See [§10](#10-contextual-actions). |
|
|
211
|
+
| `defineBreadcrumb(def)` | builder | `.use(props?, overrides?)` registers an override for a path. |
|
|
212
|
+
| `useDockGuard(opts)`, `createDockGuardRegistry()` | hook / factory | See [§12](#12-navigation-guards-and-transactions). |
|
|
213
|
+
| `createErrorStatus`, `createGuardStatus`, `ErrorActions`, `GuardActions` | factories / components | Build status descriptors and their action rows. |
|
|
214
|
+
| `DOCK_EVENTS`, `DOCK_HUD_PRIORITY`, `DOCK_HUD_RENDER_MODE`, `DOCK_HUD_VARIANT` | constants | Event names and HUD enums. |
|
|
215
|
+
|
|
216
|
+
### 5.5 Building blocks for custom surface content
|
|
217
|
+
|
|
218
|
+
| Export | Notes |
|
|
219
|
+
| :------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
220
|
+
| `DockCardHeader`, `DockCardBanner`, `DockIcon`, `DockTitle`, `DockDescription` | The same pieces the route card uses. |
|
|
221
|
+
| `useDockActionClass()` | Returns `(options?) ⇒ className` for buttons that must look like dock actions (`variant`, `className`, `tone`). Needs the theme. |
|
|
222
|
+
| `isValidBannerUrl(value)` | Banner URL guard (rejects unsafe/empty values). |
|
|
223
|
+
| `DOCK_FADE_TRANSITION`, `dockFadeVariants`, `dockListItemVariants`, `textCrossfadeVariants` | The only motion presets surface content should use ([Rule 4](../architecture-and-rules.md)). |
|
|
224
|
+
| `dockTheme` | The theme spec (for `defineTheme`). |
|
|
225
|
+
| Types | `DockItem`, `DockPageConfig`, `DockPageApi`, `DockActions`, `DockState`, `DockActionDescriptor`, `SurfaceDescriptor` / `SurfaceEntry`, `SurfaceStep`, `SurfaceResult`, `SurfaceFlowDefinitionInput`, `DockHudDescriptor`, `DockOperation`, `DockGuardDefinition`, `BreadcrumbConfig`, … (`export type * from "./types"`). |
|
|
226
|
+
|
|
227
|
+
## 6. Route cards and the page slice
|
|
228
|
+
|
|
229
|
+
### 6.1 `DockPageConfig` (the `dock` key of `usePage`)
|
|
230
|
+
|
|
231
|
+
| Field | Type | Meaning |
|
|
232
|
+
| :--------------------------------------- | :--------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
233
|
+
| `title`, `description` | `string \| ReactNode` | Header text. `title` falls back to the page-level `title`. |
|
|
234
|
+
| `icon` | iconify string (`"solar:home-2-bold"`), image URL, or node | Card icon. |
|
|
235
|
+
| `iconOverlay` | `{ icon, title?, onClick? }` | Small badge on the icon. |
|
|
236
|
+
| `badge` | `ReactNode` | Badge on the card. Update it live with `globalEvents.emit(DOCK_EVENTS.UPDATE_BADGE, { key: card.name.toLowerCase(), value, color? })` (`value` `null`/`""` hides it); needs `name`. |
|
|
237
|
+
| `banner` / `bannerUrl` | `string \| { url, position, size, repeat, opacity }` | Header banner. Object form is flattened to `bannerUrl` + `banner*`. Individual fields: `bannerPosition`, `bannerSize`, `bannerRepeat`, `bannerOpacity`. |
|
|
238
|
+
| `style` | `DockVisualStyleInput` | Per-card overrides for `card`, `icon`, `title`, `description` (`className`, CSS, `scale`, `size`), plus `active` / `inactive` / `hover` variants, `background`, `borderColor`. Merged section by section across registrations. |
|
|
239
|
+
| `className`, `width` | | Extra class; card width in px (clamped to the viewport). |
|
|
240
|
+
| `component`, `content`, `props` | | Replace the card body with your own component/node. |
|
|
241
|
+
| `action`, `headerAction` | `ReactNode \| ComponentType` | Inline action under the header / in the header. Shown only on the card that owns the current path (or overlay cards). |
|
|
242
|
+
| `actions` | `Partial<DockActionDescriptor>[]` | Toolbar actions while the page is mounted ([§10](#10-contextual-actions)). |
|
|
243
|
+
| `guard` | `{ when: boolean, message?, onBlock? }` | Navigation guard while the page is mounted ([§12](#12-navigation-guards-and-transactions)). |
|
|
244
|
+
| `surfaces` | `Record<string, SurfaceDef \| Component>` | Named surfaces opened with `page.modules.dock.surface("key", props)`. |
|
|
245
|
+
| `name` | `string` | Stable name used for search and active-item matching. |
|
|
246
|
+
| `path`, `targetPath` | `string` | `path` defaults to the current pathname. `targetPath` is where clicking navigates when it differs from `path`. |
|
|
247
|
+
| `isLoading` | `boolean` | Show the loading skeleton card. Defaults from `usePage({ loading })` (`true`, a string, or `{ isLoading }`). |
|
|
248
|
+
| `isOverlay`, `dismissible` | `boolean` | Overlay cards dim the page and ignore outside clicks. |
|
|
249
|
+
| `dockPolicy` | `{ clearTransientState?, dismissSurfaces?, prefetch? }` | Route policy used when navigating **to** this card ([§12.4](#124-route-policy)). |
|
|
250
|
+
| `keepWhenDescendant`, `prefetchDisabled` | `boolean \| (activePath, item) => boolean`, `boolean` | By default a card whose path is a **prefix of the active path** (an ancestor, except `/`) is removed from the stack so the same section is not shown twice; `keepWhenDescendant` keeps it. `prefetchDisabled` opts out of prefetch. |
|
|
251
|
+
| `registry` | `RegistryMetadata` | `priority`, `source`, `lifecycle`, … for this registration. |
|
|
252
|
+
|
|
253
|
+
How `usePage` turns the slice into registry entries (`dockModule.page`):
|
|
254
|
+
|
|
255
|
+
1. `selectPageDock` builds the slice (title fallback, banner flattening, `isLoading` from `loading`). It returns `null` when neither `dock` nor `title` is set.
|
|
256
|
+
2. `toDockEntries` strips behaviour-only fields (`actions`, `guard`, `surfaces`, `isLoading`). If nothing visual is left, **no card is registered**; the page only gets behaviour. Otherwise it registers one entry keyed by `path` (default: current pathname).
|
|
257
|
+
3. `validateDockEntry` checks field types (for example `path` must start with `/`, `width` must be a number); invalid entries are reported and rejected in `strict` mode.
|
|
258
|
+
4. When several sources register the same path, `mergeDockEntries` shallow-merges them low → high priority; `style` is merged per section (`card`, `icon`, `title`, `description`).
|
|
259
|
+
|
|
260
|
+
### 6.2 `page.modules.dock`
|
|
261
|
+
|
|
262
|
+
`usePage(...).modules.dock` is `DockPageApi`: every `DockActions` method **plus**
|
|
263
|
+
|
|
264
|
+
| Member | Description |
|
|
265
|
+
| :-------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
266
|
+
| `set(dock)` | Replace the dock slice at runtime (`page.set({ dock })`). |
|
|
267
|
+
| `surface(keyOrDef, props?)` | Open `dock.surfaces[key]`, or a definition/factory/component directly. Accepts a `defineSurface` result (uses its `.open`), a factory, a descriptor, or a bare component. |
|
|
268
|
+
|
|
269
|
+
### 6.3 Active item resolution
|
|
270
|
+
|
|
271
|
+
The active card is chosen from registered items in this order: a selected data-source item → exact `path` / `targetPath` match → longest path-prefix match → first item. On a 404 page only `/` and the not-found card are listed. While a navigation transaction is pending, the card for the **destination** is used so the dock changes before the route does.
|
|
272
|
+
|
|
273
|
+
## 7. Attention: who owns the top card?
|
|
274
|
+
|
|
275
|
+
`resolveDockAttention` scores each candidate and the highest wins. A new candidate with a higher score takes over immediately; when it goes away the next one returns.
|
|
276
|
+
|
|
277
|
+
| Kind | Score | Condition |
|
|
278
|
+
| :------------------- | :------ | :----------------------------------------------------------- |
|
|
279
|
+
| Surface | 400 | a surface is open |
|
|
280
|
+
| Status overlay | 300 + p | status with `isOverlay` (error, offline, guard, 404, custom) |
|
|
281
|
+
| Operation | 250 + p | a pending operation |
|
|
282
|
+
| HUD | 200 + p | an active HUD |
|
|
283
|
+
| Loading | 100 | the `loading` module reports `isLoading` |
|
|
284
|
+
| Status (non-overlay) | 75 + p | for example "back online" |
|
|
285
|
+
| Route | 0 | default |
|
|
286
|
+
|
|
287
|
+
`p` is the status/HUD/operation priority clamped to 0–99, so priorities never cross a band. HUD priorities: `DEFAULT 0`, `CONTEXTUAL 10`, `MEDIA 15`, `SELECTION 20`, `TASK_PROGRESS 30`, `CRITICAL 50` (`DOCK_HUD_PRIORITY`).
|
|
288
|
+
|
|
289
|
+
Besides the top card, `resolveDockScene` decides the **companion** card above the stack: a toast when a notification is visible, otherwise breadcrumbs when the stack is expanded, not an overlay, and the path has more than one crumb.
|
|
290
|
+
|
|
291
|
+
## 8. Surfaces
|
|
292
|
+
|
|
293
|
+
A **surface** is the dock's sheet: the top card shrinks its header, expands a body, and your component renders inside. It resolves a promise with a result when it closes.
|
|
294
|
+
|
|
295
|
+
### 8.1 Opening
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
const result = await openSurface(input, options?)
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
`input` can be a `SurfaceDescriptor`, a factory made by `defineSurface`, a component, or a React element. Components can also be passed in the descriptor (`component` + `props`). Invalid input resolves `{ success: false, error }` (code `DOCK_SURFACE_INVALID_COMPONENT`) and reports instead of throwing.
|
|
302
|
+
|
|
303
|
+
Opening a surface collapses and clears the expanded stack, remembers the focused element (restored on close), and pushes the new surface on a **stack**: only the top surface is interactive; earlier ones stay mounted but `inert` and hidden.
|
|
304
|
+
|
|
305
|
+
### 8.2 `SurfaceDescriptor`
|
|
306
|
+
|
|
307
|
+
| Field | Default | Meaning |
|
|
308
|
+
| :--------------------------------------------------------------------- | :---------------- | :--------------------------------------------------------------------------------------------------- |
|
|
309
|
+
| `component` + `props` / `content` / `node` / `element` | | What to render. One of them is required (or `steps`). |
|
|
310
|
+
| `title`, `description`, `icon`, `header: { title, description, icon }` | | Header. `descriptionMaxLines` defaults to 2. |
|
|
311
|
+
| `id` | | Identity for `useSurface(id)` and flows. |
|
|
312
|
+
| `action`, `showAction` | `null` | Action under the body; `showAction: true` forces it visible. |
|
|
313
|
+
| `trailing`, `headerAction`, `badge` | | Header slots. |
|
|
314
|
+
| `extensions` | `[]` | Shelf items under the card (`SurfaceExtension`, element, or array). |
|
|
315
|
+
| `closeLabel` | `"Close surface"` | Accessible label of the close button. |
|
|
316
|
+
| `width` | card width | Number (px) or CSS size; clamped to the viewport. |
|
|
317
|
+
| `dismissible` | `true` | `false` removes the close button, outside-click and Esc dismissal. |
|
|
318
|
+
| `allowSwipeDismiss` | `true` | Drag down to dismiss (ignored on inputs, sliders, `[data-no-surface-drag]`, `[data-lenis-prevent]`). |
|
|
319
|
+
| `skipActionDismiss` | `true` | Skip the "dismiss action" opening phase. |
|
|
320
|
+
| `steps`, `currentStepIndex` | | Wizard ([§8.5](#85-steps-wizards)). |
|
|
321
|
+
| `syncWithUrl`, `urlKey` | `false` | Mirror the open state in `?surface=<value>` ([§8.7](#87-url-sync)). |
|
|
322
|
+
| `onClose(result)` | | Called once when the surface is released. |
|
|
323
|
+
|
|
324
|
+
### 8.3 What a surface component receives
|
|
325
|
+
|
|
326
|
+
Your component gets these props (plus your own `props`, which win on conflicts):
|
|
327
|
+
|
|
328
|
+
`close(result?)`, `closeAll(result?)`, `pushStep(step)`, `popStep()`, `goToStep(index)`, `stepIndex`, `totalSteps`, `isFirstStep`, `isLastStep`, `surfaceWidth`.
|
|
329
|
+
|
|
330
|
+
### 8.4 Results and closing
|
|
331
|
+
|
|
332
|
+
`SurfaceResult` is `unknown`, by convention:
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
{ success: boolean; data?: unknown; cancelled?: boolean; reason?: string; error?: unknown }
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
The dock itself closes surfaces with these reasons: `"dock"` (route change, programmatic close from navigation), `"browser-back"`, `"unmount"` (provider unmounted), `"guard"` (user chose to stay), `"flow-cancelled"`. A user closing with the X, Esc, swipe or backdrop resolves `null`/`undefined` unless your component calls `close(result)`. Always treat a falsy result as "dismissed".
|
|
339
|
+
|
|
340
|
+
Focus returns to the element that opened the surface after the last surface closes, except for `browser-back`, `dock`, `unmount`.
|
|
341
|
+
|
|
342
|
+
Lifecycle phases (`DOCK_SURFACE_PHASE`): `idle → dismissing_action → expanding_body → open → closing_anticipation → collapsing_body → restoring_header → idle`. They drive animation and height locking and are exposed as `surfacePhase` in `DockState`.
|
|
343
|
+
|
|
344
|
+
### 8.5 Steps (wizards)
|
|
345
|
+
|
|
346
|
+
```tsx
|
|
347
|
+
const Onboarding = defineStepSurface({
|
|
348
|
+
id: "onboarding",
|
|
349
|
+
title: ({ name }) => `Welcome, ${name}`,
|
|
350
|
+
steps: [
|
|
351
|
+
{ id: "profile", component: ProfileStep, title: "Profile" },
|
|
352
|
+
{ id: "avatar", component: AvatarStep, title: "Avatar" },
|
|
353
|
+
{ id: "done", content: <Done />, title: "All set" },
|
|
354
|
+
],
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const [open] = useSurface(Onboarding);
|
|
358
|
+
open({ name: "Ada" });
|
|
359
|
+
|
|
360
|
+
function ProfileStep() {
|
|
361
|
+
const { next, prev, isFirst, isLast, stepIndex, stepsTotal, close } = useSurfaceStep();
|
|
362
|
+
…
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
- `next()` is a no-op on the last step, `prev()` on the first.
|
|
367
|
+
- `goTo(i)` ignores out-of-range or non-integer indexes.
|
|
368
|
+
- `push(step)` appends a step at runtime and jumps to it; the original body becomes step 0.
|
|
369
|
+
- The header back button pops a step; at step 0 with more than one surface it closes the top surface.
|
|
370
|
+
|
|
371
|
+
### 8.6 Flows
|
|
372
|
+
|
|
373
|
+
A **flow** is a surface with an id, a serialisable `snapshot`, and optional return behaviour. Use it when state must survive a reload or when the user leaves for another page and comes back (OAuth, verification).
|
|
374
|
+
|
|
375
|
+
```tsx
|
|
376
|
+
const verifyFlow = {
|
|
377
|
+
id: "verify-email",
|
|
378
|
+
createSurface: ({ snapshot, input }) => ({
|
|
379
|
+
component: VerifyEmail,
|
|
380
|
+
props: { snapshot, input },
|
|
381
|
+
syncWithUrl: "verify",
|
|
382
|
+
}),
|
|
383
|
+
initialSnapshot: { step: "code" },
|
|
384
|
+
returnTo: "/account", // or returnHandshake: { pathname, focusKey, restoreScroll, returnOnCancel }
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
const flow = useSurfaceFlow(verifyFlow); // restores itself on mount if history says so
|
|
388
|
+
await flow.open({ email });
|
|
389
|
+
flow.update({ step: "done" }); // snapshot must be a plain object
|
|
390
|
+
flow.complete({ verified: true }); // resolves { success: true, data }
|
|
391
|
+
flow.cancel(); // resolves { cancelled: true, reason: "flow-cancelled", success: false }
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
| Option | Default | Meaning |
|
|
395
|
+
| :----------------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
396
|
+
| `singleton` | `true` | Re-opening returns the existing promise instead of opening twice. |
|
|
397
|
+
| `restoreFromUrl` | `true` | `useSurfaceFlow` re-opens the flow once on mount when `history.state.dockSurface` carries this flow id **and** `?surface=` still matches. |
|
|
398
|
+
| `returnHandshake` / `returnTo` | none | Where to go after the flow settles. `restoreScroll` defaults to `true`, `returnOnCancel` to `false`. The flow input may override with `returnTo`, `returnFocusKey`, `restoreReturnScroll`, `returnOnCancel`, `returnHandshake`. |
|
|
399
|
+
|
|
400
|
+
After a flow settles successfully (or cancelled with `returnOnCancel`), the dock delivers a **return handoff** for `handshake.pathname` and navigates there (or restores in place if already there). Dock-initiated closes (`dock`, `browser-back`, `unmount`) never trigger a return. The destination page reads it with `useSurfaceReturn()`.
|
|
401
|
+
|
|
402
|
+
Imperative equivalents on `DockActions`: `openSurfaceFlow(def, input)`, `restoreSurfaceFlow(def)`, `updateSurfaceFlow(id, snapshot)`, `completeSurfaceFlow(id, data)`, `cancelSurfaceFlow(id, data)`, `getSurfaceFlow(id)` (handle with `isOpen`, `snapshot`, `status`, `update`, `complete`, `cancel`). Invalid definitions resolve `{ success: false, error }` with codes `DOCK_SURFACE_FLOW_INVALID_DEFINITION`, `DOCK_SURFACE_FLOW_UNAVAILABLE`, `DOCK_SURFACE_FLOW_ORPHANED`.
|
|
403
|
+
|
|
404
|
+
### 8.7 URL sync
|
|
405
|
+
|
|
406
|
+
`syncWithUrl: true` (or `urlKey: "x"`, or `syncWithUrl: "x"`) pushes `?surface=<value>` (`open` when nothing else is given) with `history.pushState`, and stores `{ dockSurface: { value, flow? } }` in history state. On close the previous `surface` value is restored (or the param removed). Pressing browser back while such a surface is open closes **all** surfaces with reason `browser-back`.
|
|
407
|
+
|
|
408
|
+
### 8.8 Slots inside surface content
|
|
409
|
+
|
|
410
|
+
```tsx
|
|
411
|
+
function InviteForm({ close }) {
|
|
412
|
+
return (
|
|
413
|
+
<>
|
|
414
|
+
<DockSurfaceHeaderButton icon="solar:question-circle-bold" onClick={openHelp} />
|
|
415
|
+
<DockSurfaceExtension id="invite-tabs" align="center"><Tabs … /></DockSurfaceExtension>
|
|
416
|
+
<DockSurfaceAction><Button onClick={() => close({ success: true })}>Send</Button></DockSurfaceAction>
|
|
417
|
+
<motion.ul variants={dockFadeVariants} initial="hidden" animate="visible">…</motion.ul>
|
|
418
|
+
</>
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Extensions declared in the descriptor and those mounted via `<DockSurfaceExtension>` are merged by `id` (mounted wins) and sorted by `order`. They appear only while the body is visible.
|
|
424
|
+
|
|
425
|
+
## 9. HUD
|
|
426
|
+
|
|
427
|
+
A **HUD** is a compact heads-up card (height 52 px) that temporarily takes the top card: selection mode, "3 items selected", undo, upload progress.
|
|
428
|
+
|
|
429
|
+
### 9.1 Declarative: `useDockHud`
|
|
430
|
+
|
|
431
|
+
```tsx
|
|
432
|
+
useDockHud(
|
|
433
|
+
selected.length
|
|
434
|
+
? {
|
|
435
|
+
id: "selection",
|
|
436
|
+
component: SelectionBar,
|
|
437
|
+
props: { count: selected.length },
|
|
438
|
+
priority: DOCK_HUD_PRIORITY.SELECTION,
|
|
439
|
+
}
|
|
440
|
+
: null,
|
|
441
|
+
);
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
The HUD is shown while the descriptor is active and removed on unmount; a `null`/inactive descriptor removes it. Equal descriptors (shallow-compared props, functions treated as equal) do not re-publish.
|
|
445
|
+
|
|
446
|
+
### 9.2 Reusable: `defineHud` / `useHud`
|
|
447
|
+
|
|
448
|
+
```tsx
|
|
449
|
+
const UndoHud = defineHud({
|
|
450
|
+
id: "undo",
|
|
451
|
+
component: UndoBar,
|
|
452
|
+
autoDismissMs: 6000,
|
|
453
|
+
priority: DOCK_HUD_PRIORITY.CONTEXTUAL,
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
const hud = useHud(UndoHud);
|
|
457
|
+
const handle = hud.show({ label: "Deleted" }); // → { id, update(props), dismiss(clearFn) }
|
|
458
|
+
handle?.update({ label: "Restoring…" });
|
|
459
|
+
hud.hide(); // or hud.clear() for everything
|
|
460
|
+
UndoHud.use({ label: "…" }); // mounted-while-rendered variant
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
`useHud()` without a definition returns the raw `{ setHud, clearHud }`.
|
|
464
|
+
|
|
465
|
+
### 9.3 Descriptor reference
|
|
466
|
+
|
|
467
|
+
| Field | Default | Meaning |
|
|
468
|
+
| :------------------------------------------------------- | :----------------------- | :------------------------------------------------------------ |
|
|
469
|
+
| `id` | `"hud"` / component name | Identity (same id replaces). |
|
|
470
|
+
| `component` + `props`, or `content` / `node` / `element` | | What to render. |
|
|
471
|
+
| `priority` | `0` | Highest active wins; also adds to the attention score (0–99). |
|
|
472
|
+
| `autoDismissMs` | `null` | Auto-dismiss timer (`> 0`). |
|
|
473
|
+
| `dismissOnEscape` | `true` | Esc dismisses (capture phase, stops propagation). |
|
|
474
|
+
| `dismissOnNavigate` | `true` | Route change dismisses. |
|
|
475
|
+
|
|
476
|
+
A HUD `component` receives your `props` plus an `onDismiss()` callback that runs `onDismiss`/`onCancel` and removes the HUD.
|
|
477
|
+
| `onDismiss` / `onCancel` | | Called on dismissal. |
|
|
478
|
+
| `isActive` | `true` | `false` hides it. |
|
|
479
|
+
|
|
480
|
+
All transient HUDs and selection mode are cleared on a route change unless the target card's `dockPolicy.clearTransientState` is `false`.
|
|
481
|
+
|
|
482
|
+
### 9.4 Operations
|
|
483
|
+
|
|
484
|
+
```ts
|
|
485
|
+
const { operations } = useDockActions();
|
|
486
|
+
const op = operations.start({ id?, label: "Exporting", description?, icon?, progress: 0, hud?, cancellable: true, onCancel, priority: 0, metadata });
|
|
487
|
+
operations.update(op.id, { progress: 0.4, label: "Almost there" }); // only while pending
|
|
488
|
+
operations.complete(op.id, result);
|
|
489
|
+
operations.cancel(op.id, reason); // calls onCancel (async errors are reported), marks cancelled
|
|
490
|
+
operations.clear(op.id); // remove one entry; clear() removes all
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
- `progress` is clamped to 0–1 (or `null` for indeterminate); `label` defaults to `"Working"`; ids default to `dock-operation-N`; at most 24 entries are kept.
|
|
494
|
+
- The operation with the highest `priority` (ties: oldest) is "active". Its `hud` (descriptor, component, or node) is rendered as the HUD `dock-operation:<id>` with priority `30 + operation.priority`. The HUD component receives `operation` and `pendingCount` props.
|
|
495
|
+
- Operation HUDs ignore Esc and navigation; if `cancellable !== false` dismissing the HUD cancels the operation.
|
|
496
|
+
- Always pass a `hud`: an operation without one is tracked in `state.operations` but has nothing to render.
|
|
497
|
+
|
|
498
|
+
## 10. Contextual actions
|
|
499
|
+
|
|
500
|
+
Toolbar buttons on the top card while the page is mounted.
|
|
501
|
+
|
|
502
|
+
```tsx
|
|
503
|
+
const Share = defineDockAction({
|
|
504
|
+
key: "share",
|
|
505
|
+
icon: "solar:share-bold",
|
|
506
|
+
tooltip: "Share",
|
|
507
|
+
order: 10,
|
|
508
|
+
});
|
|
509
|
+
Share.use(() => navigator.share({ url: location.href })); // register while mounted (arg = onClick or overrides)
|
|
510
|
+
Share.create({ disabled: !canShare }); // a descriptor, for dock.actions
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
or `usePage({ dock: { actions: [{ key: "save", icon, onClick }] } })`, or `useDockContextActions([...])`.
|
|
514
|
+
|
|
515
|
+
`DockActionDescriptor`: `key`, `icon`, `label`, `tooltip`, `order` (ascending), `visible`, `disabled`, `tone`, `badge`, `className`, `onClick`. Actions without a `key` get a positional key; a changed list unregisters keys that disappeared and everything is unregistered on unmount. Imperative: `registerContextAction`, `unregisterContextAction`, `setContextActions`, `clearContextActions`.
|
|
516
|
+
|
|
517
|
+
## 11. Status overlays
|
|
518
|
+
|
|
519
|
+
A **status** is a card that interrupts the route: error, offline, 404, guard confirmation, or anything you emit. Only one status is held; a new status replaces the current only if its priority is **greater or equal**.
|
|
520
|
+
|
|
521
|
+
| Type | Priority | Source |
|
|
522
|
+
| :---------------------------- | :------- | :------------------------------------------------------- |
|
|
523
|
+
| `GUARD` | 120 | `DOCK_EVENTS.GUARD`, `navigate()` blocked by a guard |
|
|
524
|
+
| `ACCOUNT_DELETE` | 115 | `DOCK_STATUS_SET` |
|
|
525
|
+
| `LOGIN` / `SIGNUP` / `LOGOUT` | 110 | `DOCK_STATUS_SET` |
|
|
526
|
+
| `APP_ERROR` | 100 | `EVENT_TYPES.APP_ERROR` (error boundaries) |
|
|
527
|
+
| `NOT_FOUND` | 97 | `DOCK_EVENTS.NOT_FOUND` |
|
|
528
|
+
| `API_ERROR` | 95 | `EVENT_TYPES.API_ERROR` with `isCritical` |
|
|
529
|
+
| `OFFLINE` | 90 | `window` `offline` event (also checked on mount) |
|
|
530
|
+
| `ONLINE` | 10 | `window` `online` (non-overlay, auto-clears after 4.5 s) |
|
|
531
|
+
|
|
532
|
+
Events (all through `globalEvents` from `@omerdlw/base-framework/events`, usable outside React):
|
|
533
|
+
|
|
534
|
+
| Event | Payload | Behaviour |
|
|
535
|
+
| :---------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
536
|
+
| `DOCK_STATUS_SET` | `{ type = "STATUS", title, description, icon, priority, flow, isOverlay = true, action, actions, style, themeType, duration, persist }` | `duration` default 3000 ms; `0` or `null` = stays until cleared. `persist !== false` stores non-error statuses with a string icon in `sessionStorage["dock_overlay_status"]` and restores them (with the remaining time) after a reload. |
|
|
537
|
+
| `DOCK_STATUS_CLEAR` | `{ type?, flow? }` | Clears the matching status; no filter clears whatever is shown. |
|
|
538
|
+
| `DOCK_NOT_FOUND` | `{ title = "404", description, icon, clear? }` | Shows the 404 card (`clear: true` removes it). The starter `src/app/not-found.tsx` renders nothing and emits this on mount (one tick later, because the dock subscribes after the page's effects) and `{ clear: true }` on unmount, so unknown URLs show an empty page with the dock's 404 card instead of the Next.js 404 screen. The default dock module installs `NotFoundActions` ("Go back" = `router.back()`, "Home" = forced navigate to `/`) as the card's action through `DockProvider`'s `notFoundAction` prop. |
|
|
539
|
+
| `DOCK_GUARD` | `{ title, message, icon, confirmText, cancelText, onConfirm, onCancel, clear? }` | Navigation-blocked card. `navigate()` emits it for you. |
|
|
540
|
+
| `EVENT_TYPES.API_ERROR` | `{ isCritical, status, message, retry }` | Only critical errors; batched for 300 ms into one card ("Some requests failed" for many). Retry re-runs every `retry`. |
|
|
541
|
+
| `EVENT_TYPES.APP_ERROR` | `{ message, error, resetError }` | "Something went wrong" with Retry (`resetError`) and Refresh. |
|
|
542
|
+
|
|
543
|
+
Error statuses (`GUARD`, `ACCOUNT_DELETE`, `APP_ERROR`, `API_ERROR`, `NOT_FOUND`) are cleared on route change (except `ACCOUNT_DELETE`) and are never persisted. `createErrorStatus` / `createGuardStatus` build descriptors with `ErrorActions` / `GuardActions` rows; their labels come from `retryLabel` / `refreshLabel` / `confirmText` / `cancelText`.
|
|
544
|
+
|
|
545
|
+
The starter `src/app/error.tsx` (Next.js's segment boundary, which catches page errors before `ErrorBoundaryCore` sees them) renders nothing and emits `APP_ERROR` with `resetError: reset`, so a crashing page shows the dock's error card with **Refresh** / **Retry**. The default dock module registers `ErrorActions` and `GuardActions` as `statusActionDefaults` (in `module.tsx`); without that registration error and guard cards have no buttons.
|
|
546
|
+
|
|
547
|
+
## 12. Navigation, guards and transactions
|
|
548
|
+
|
|
549
|
+
### 12.1 `navigate(href, { force?, item?, source? })`
|
|
550
|
+
|
|
551
|
+
Returns `Promise<boolean>` — `true` only when the route change was committed.
|
|
552
|
+
|
|
553
|
+
1. Rejects unsafe or non-internal hrefs and the current location (`false`).
|
|
554
|
+
2. Starts a **transaction** `{ id, from, to, source, status }` (superseding any pending one).
|
|
555
|
+
3. Unless `force`, awaits every registered guard. If one blocks → the transaction is cancelled with reason `guard`, a `GUARD` status is shown, and the promise resolves `false`. "Leave" later re-commits through the same path (source `guard-confirmation`).
|
|
556
|
+
4. Commit: remember scroll/focus of the old path, blur the active element, prepare the route reset policy, emit `DOCK_NAVIGATE_START`, `router.push`, emit `DOCK_NAVIGATE`.
|
|
557
|
+
5. `DOCK_NAVIGATE_END` is emitted when the location key changes (or in the next frame for same-path hash/query changes); the `loading` module is told to stop.
|
|
558
|
+
|
|
559
|
+
A transaction times out after 15 s (`stopLoading` is called). Statuses: `pending → completed | cancelled | failed | timed-out`. Expanding or navigating collapses the stack and clears hover/search.
|
|
560
|
+
|
|
561
|
+
### 12.2 Guards
|
|
562
|
+
|
|
563
|
+
```ts
|
|
564
|
+
const { isActive, setGuard, clearGuard } = useDockGuard({
|
|
565
|
+
when: isDirty, // boolean or (to, from) => boolean | Promise<boolean>
|
|
566
|
+
message: "Discard your changes?",
|
|
567
|
+
onBlock: ({ to, from, guardId, message }) => track(to),
|
|
568
|
+
});
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
- Guards live in a per-`DockProvider` registry; they are evaluated in registration order and the **first** that blocks wins. A guard that throws counts as blocking (`reason: "error"`).
|
|
572
|
+
- `useDockGuard` also installs a `beforeunload` prompt while `when` is truthy, and emits `DOCK_GUARD { clear: true }` when it turns falsy or unmounts.
|
|
573
|
+
- Page-level: `usePage({ dock: { guard: { when, message, onBlock } } })`.
|
|
574
|
+
- Imperative: `registerGuard(def)` returns an unregister function; `clearDockGuards()` drops all.
|
|
575
|
+
- `navigate(href, { force: true })` skips guards.
|
|
576
|
+
|
|
577
|
+
### 12.3 Prefetch
|
|
578
|
+
|
|
579
|
+
Hovering a card for 90 ms prefetches its `path` with `router.prefetch` (leaving the card cancels a pending prefetch); focusing a card with the keyboard prefetches its `targetPath ?? path` **immediately**. Not prefetched: loading, overlay and surface cards, cards with `prefetchDisabled` or `dockPolicy.prefetch: false`. A route is prefetched once; if Next.js invalidates it, it can be prefetched again.
|
|
580
|
+
|
|
581
|
+
### 12.4 Route policy
|
|
582
|
+
|
|
583
|
+
`resolveDockRoutePolicy` merges defaults with the destination card's `dockPolicy`. The card is known when the navigation starts from the stack or when you call `navigate(href, { item })`; a bare `navigate(href)` always uses the defaults.
|
|
584
|
+
|
|
585
|
+
| Key | Default | Effect on route change |
|
|
586
|
+
| :-------------------- | :-------------------- | :------------------------------------- |
|
|
587
|
+
| `dismissSurfaces` | `true` | Close all surfaces with reason `dock`. |
|
|
588
|
+
| `clearTransientState` | `true` | Clear HUDs and selection mode. |
|
|
589
|
+
| `prefetch` | `true` (when allowed) | Allow intent prefetch. |
|
|
590
|
+
|
|
591
|
+
### 12.5 Keyboard and pointer
|
|
592
|
+
|
|
593
|
+
| Input | Result |
|
|
594
|
+
| :--------------------------------------- | :------------------------------------------------------------------------------------------------------------------ |
|
|
595
|
+
| Click top card | Expand the stack. |
|
|
596
|
+
| Click a card while expanded | Navigate to its `targetPath ?? path`. |
|
|
597
|
+
| `Esc` (expanded) | Collapse. |
|
|
598
|
+
| `↑` / `↓` (expanded) | Move focus through cards, wrapping. |
|
|
599
|
+
| `Enter` (expanded, card focused) | Navigate to the focused card. |
|
|
600
|
+
| `Enter` / `Space` on a Tab-focused card | Same as clicking that card (cards are keyboard-activatable). |
|
|
601
|
+
| Click outside | Close the surface (unless `dismissible: false`), otherwise collapse. |
|
|
602
|
+
| Surface open: `Esc`, drag down, backdrop | Close (when dismissible). Tab is trapped inside the surface; elements with `data-dock-autofocus` get initial focus. |
|
|
603
|
+
|
|
604
|
+
Shortcuts ignore events from inputs/editable targets. The stack collapses automatically while an element is fullscreen. Nothing polls on an interval (a test enforces this).
|
|
605
|
+
|
|
606
|
+
## 13. Continuity and return handoffs
|
|
607
|
+
|
|
608
|
+
`useDockActions().continuity`:
|
|
609
|
+
|
|
610
|
+
| Method | Description |
|
|
611
|
+
| :-------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- |
|
|
612
|
+
| `remember(path, { scrollY, focusKey, snapshot })` | Save scroll/focus (called automatically for the old path on every committed navigation; at most 32 paths). |
|
|
613
|
+
| `get(path)`, `restore(path, { focusKey, restoreScroll })` | Read / re-apply a snapshot (scroll position, and focus of the element whose `data-dock-focus-key` matches). |
|
|
614
|
+
| `deliverReturn(path, { data, flowId, status })` | Queue a handoff for a path (at most 16). |
|
|
615
|
+
| `getReturns(path)`, `consumeReturn(path, id?)` | Read / remove handoffs. Pages normally use `useSurfaceReturn()`. |
|
|
616
|
+
| `remove(path)`, `clear()` | Forget. |
|
|
617
|
+
|
|
618
|
+
## 14. Breadcrumbs
|
|
619
|
+
|
|
620
|
+
Generated from the pathname: `Home` (`/`) plus one crumb per segment, titled by `formatSlugTitle` (`my-team` → `My Team`). The breadcrumbs card is shown when expanded, not an overlay, no toast is visible, and there is more than one crumb. With more than `maxItems` (4) crumbs the middle collapses to `…`.
|
|
621
|
+
|
|
622
|
+
```ts
|
|
623
|
+
// customise globally (DockProvider prop)
|
|
624
|
+
const breadcrumbConfig: BreadcrumbConfig = {
|
|
625
|
+
root: { title: "Dashboard", icon: "solar:home-2-bold" },
|
|
626
|
+
resolveSegment: ({ segment, index }) =>
|
|
627
|
+
segment === "u" ? { title: "People" } : null,
|
|
628
|
+
resolvePath: ({ pathname, root, segments }) => null, // return crumbs to take over entirely
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
// override one path from a page
|
|
632
|
+
const TeamCrumb = defineBreadcrumb({
|
|
633
|
+
title: ({ name }) => name,
|
|
634
|
+
icon: "solar:users-group-rounded-bold",
|
|
635
|
+
});
|
|
636
|
+
TeamCrumb.use({ name: team.name }); // path defaults to the current pathname
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
Priority per crumb: generated → `resolveSegment` → `defineBreadcrumb` override. `useDockBreadcrumbs()` (internal) returns `{ breadcrumbs, current, parent, canGoBack, goBack }`.
|
|
640
|
+
|
|
641
|
+
## 15. Media integration
|
|
642
|
+
|
|
643
|
+
The dock reads the `media` module through `definePeer` (inert when not installed):
|
|
644
|
+
|
|
645
|
+
- While a media session exists (`hasMedia`) the **top card** that owns the current path shows the built-in media controls in its action slot: play/pause (also by clicking the card icon or the card itself), seek bar with time tooltip, ±10 s skip, volume (drag, ←/→/↑/↓ in 5 % steps, mute toggles and restores 70 % from zero), loop, playback speed (cycles 1 → 1.25 → 1.5 → 2), and Picture-in-Picture for video.
|
|
646
|
+
- Clicking the top card's icon or the card itself toggles playback while media exists. A card's `mediaAction: false` only suppresses the optional `DockProvider` `mediaAction` component, not the built-in controls.
|
|
647
|
+
- `DockProvider`'s `mediaAction` prop adds a further component next to the inline action (rarely needed).
|
|
648
|
+
|
|
649
|
+
See [media.md](./media.md) for how sources are registered (`usePage({ media })`, `MediaSource`, the background module's video).
|
|
650
|
+
|
|
651
|
+
## 16. Layout and geometry
|
|
652
|
+
|
|
653
|
+
| What | Value |
|
|
654
|
+
| :-------------------- | :-------------------------------------------------------------------------------------- |
|
|
655
|
+
| Card base height | 68 px; HUD 52 px; chrome 20 px; viewport margin 24 px |
|
|
656
|
+
| Card width | 460 px (clamped to `viewport − 16/32 px`), or the card/surface `width` |
|
|
657
|
+
| Visible stacked cards | 3 (`MAX_VISIBLE_STACKED_CARDS`); collapsed scale 0.88 per level, opacity −0.2 per level |
|
|
658
|
+
| Published height | `--dock-h` on `<html>` and `dockHeight` in state (card height + 16 px buffer) |
|
|
659
|
+
|
|
660
|
+
Reserve space with `useDockHeight().padding` or `padding-bottom: var(--dock-h)`. Height changes are measured with a `ResizeObserver` and ignored below 0.5 px; during surface close phases the height is locked to the base height.
|
|
661
|
+
|
|
662
|
+
## 17. Theming
|
|
663
|
+
|
|
664
|
+
The module contains **no design classes**. Every slot comes from `dockTheme` (`src/config/dock.module.theme.ts`, 101 slots, named after the component that renders them): `stack`, `backdrop`, `card*`, `banner*`, `header*`, `title*`, `description*`, `icon*` (incl. `iconBadge`, `iconOverlay*`), `command*`, `status*`, `surface*`, `control*` (incl. `controlHeaderButton`), `extension*`, `scrubber*`, `media*`, `breadcrumbs*`, `loading*`, `action*`. State variants are `data-*` modifiers on the slot itself (`data-placement`, `data-blur`, `data-shelf`, `data-anchored`, `data-ghost`, `data-fill`, `data-active`, `data-banner`, `data-multiline`, `data-interactive`, `data-text`); the header of `src/config/dock.module.theme.ts` lists them. `styles` can add inline CSS per slot (z-index, banner masks, stack size/radius).
|
|
665
|
+
|
|
666
|
+
Stays in code: stack motion (offsets, scale, opacity), measured geometry, per-position z-index (`Z_INDEX.DOCK_CARD_STACK_BASE − position`), compositor hints, the skeleton pulse class. All motion presets derive from `@omerdlw/base-framework/tokens` ([Rule 4](../architecture-and-rules.md)). The theme is **required**: a missing theme throws `Theme "dock" is missing…`.
|
|
667
|
+
|
|
668
|
+
Per-card overrides use `style` on the card (see [§6.1](#61-dockpageconfig-the-dock-key-of-usepage)). Custom buttons inside surfaces should use `useDockActionClass()` so they match.
|
|
669
|
+
|
|
670
|
+
## 18. Recipes and gotchas
|
|
671
|
+
|
|
672
|
+
| I want to… | Do this |
|
|
673
|
+
| :--------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- |
|
|
674
|
+
| Show a card for a page without a visible title | Register only behaviour: `dock: { actions, guard }`. No card is added. |
|
|
675
|
+
| Open a surface from a server-driven event | `useDockActions().openSurface(...)` inside an effect; it returns a promise, so use `void`/`await` and ignore a falsy result. |
|
|
676
|
+
| Make sure a result is meaningful | Always `close({ success: true, data })` on success; treat `null` as dismissed. |
|
|
677
|
+
| Keep a surface when the route changes | Set the **destination** card's `dockPolicy: { dismissSurfaces: false }` (and navigate through that card, or pass `{ item }`). |
|
|
678
|
+
| Show a status from an API client | Emit `EVENT_TYPES.API_ERROR` with `isCritical: true` and a `retry`. |
|
|
679
|
+
| Hide the card entirely on a page | There is no switch; register an `isOverlay` surface or use a status instead. |
|
|
680
|
+
| Test dock code | Use the harness in `tests/support` and pass a fake `scheduler` to `DockProvider`. |
|
|
681
|
+
|
|
682
|
+
Gotchas:
|
|
683
|
+
|
|
684
|
+
- `useDockActions()` throws outside `DockProvider`; `useDockSelector()` re-renders only for the slice you select.
|
|
685
|
+
- `useDock()` is for the dock view only; calling it in a page re-runs the whole view model.
|
|
686
|
+
- `defineSurface().use()` / `defineHud().use()` are hooks; `.create()` / `.open()` are not.
|
|
687
|
+
- A flow `snapshot` must be a plain object; anything else becomes `null`.
|
|
688
|
+
- Status events are global: remember to emit `DOCK_STATUS_CLEAR` for sticky (`duration: 0`) statuses.
|
|
689
|
+
- Guards registered by an unmounted page disappear with it; `clearDockGuards()` drops every guard, including other pages'.
|
|
690
|
+
- No arbitrary durations or `z-*` classes in surface content: use the exported motion presets and `Z_INDEX` ([Rules 4 and 5](../architecture-and-rules.md)).
|
|
691
|
+
|
|
692
|
+
## 19. File map
|
|
693
|
+
|
|
694
|
+
`src/modules/dock/` follows the module slots (`types`, `context`, `state`, `utils`, `constants`, `overlay`, `module`, `index`) and adds `provider`, `hooks`, `motion`, `cards`, `hud` and the folders `runtime/`, `routing/`, `status/`, `surface/`. Architecture tests enforce that dock folders take `types`/`constants`/`utils` from the root and never import composition files.
|
|
695
|
+
|
|
696
|
+
| File | Responsibility |
|
|
697
|
+
| :------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
698
|
+
| `index.ts` | Public barrel (named exports only). |
|
|
699
|
+
| `types.ts` | All contracts (`DockItem`, `DockActions`, `DockState`, surfaces, HUD, status, guards, theme slots). The one deliberate `any` is `DockComponent`. |
|
|
700
|
+
| `constants.ts` | Event names, attention kinds/priorities, surface phases, transaction/operation enums, card dimensions, status priorities, timeouts, `dockTheme`. |
|
|
701
|
+
| `utils.ts` | Path helpers, safe-href check, banner validation, component/renderable resolution, style helpers, focus helpers, item ordering, surface/extension normalization, return handshakes, `createDockScheduler`. |
|
|
702
|
+
| `context.ts` | `DockContext`, `EMPTY_DOCK_STATE`, state hooks, and the media / loading / notification **peers**. |
|
|
703
|
+
| `state.ts` | Pure logic: `resolveDockAttention`, `resolveDockScene`, operation factory and reducer. |
|
|
704
|
+
| `hooks.ts` | Public hooks (`useDockRegistration`, `useDockConfig`, `useDockBanner`, `useDockDimensions`, `useSurfaceReturn`, `useDockActionClass`), plus internal focus trap, keyboard, height controller, viewport, style resolution. |
|
|
705
|
+
| `provider.tsx` | `DockProvider`: composes route bridge, expansion, operations, HUD registry, command registry, surface stack, continuity; publishes `DockState` to a store. |
|
|
706
|
+
| `module.tsx` | `dockModule`, page slice (`selectPageDock`, `toDockEntries`, `validateDockEntry`, `mergeDockEntries`, `openPageSurface`), event-map and kernel type augmentation. |
|
|
707
|
+
| `overlay.tsx` | `Dock` and every rendered piece: cards, header, banner, command bar, surface shell/controls/extensions, HUD view, media controls and scrubber, breadcrumbs card, `ErrorActions`, `GuardActions`. |
|
|
708
|
+
| `cards.tsx` | View models for the card components (banner, header, command bar, item, media controls/scrubber), `applyMediaAction`, action-slot resolution. |
|
|
709
|
+
| `hud.ts` | HUD descriptors, registry hook, `defineHud`, `useHud`, `useDockHud`, HUD lifecycle and view model, operation HUD. |
|
|
710
|
+
| `motion.ts` | Dock durations, springs, transitions, variants and factories, all derived from `@omerdlw/base-framework/tokens`. |
|
|
711
|
+
| `runtime/dock.tsx` | `useDock` (view controller), `useDockModel`, layout ordering, expansion, operation state, route bridge. |
|
|
712
|
+
| `runtime/display.ts` | Item pipeline: registry entries → filtered items → active item (applies surface / status / media). |
|
|
713
|
+
| `runtime/commands.ts` | Context-action registry, `useDockContextActions`, `defineDockAction`. |
|
|
714
|
+
| `routing/navigation.ts` | `useDockNavigation`, route policy, topology, prefetch, location key, view connection. |
|
|
715
|
+
| `routing/transactions.ts` | Navigation transaction reducer and timeout. |
|
|
716
|
+
| `routing/guards.ts` | Guard registry, `useDockGuard`, guard confirmation. |
|
|
717
|
+
| `routing/continuity.ts` | Scroll/focus snapshots and return handoffs. |
|
|
718
|
+
| `routing/breadcrumbs.tsx` | Breadcrumb resolution, provider, overrides, `defineBreadcrumb`. |
|
|
719
|
+
| `status/model.tsx` | Status factories, priorities, persistence, `applyStatusOverlay`. |
|
|
720
|
+
| `status/events.tsx` | Event subscriptions that turn app events into statuses. |
|
|
721
|
+
| `status/hooks.ts` | `useDockStatus`: the status state machine, timers, restoration. |
|
|
722
|
+
| `surface/definition.ts` | Normalization, `defineSurface`, `defineStepSurface`, `useSurface`, `useSurfaceStep`, flow definitions/sessions. |
|
|
723
|
+
| `surface/machine.ts` | Pure transition machine (open/close/close-all/advance → state + effects), URL sync, resource release. |
|
|
724
|
+
| `surface/stack.ts` | `useSurfaceStack`: the imperative API (`openSurface`, steps, close choreography, focus restore). |
|
|
725
|
+
| `surface/hooks.tsx` | Flow operations, lifecycle effects (back button, unmount), `useSurfaceFlow`, extension/shell models. |
|
|
726
|
+
| `surface/context.tsx` | Surface item context, header-action and extension stores, `useSurface*` hooks. |
|
|
727
|
+
| `surface/view-model.ts` | `resolveSurfaceViewModel` / `applySurfaceToDockItem`: surface state → dock item. |
|
|
728
|
+
|
|
729
|
+
## 20. Dependencies
|
|
730
|
+
|
|
731
|
+
- **Uses:** `@omerdlw/base-framework/kernel` (`defineModule`, `definePeer`, `useRegistryEntries`, `useModuleRegistration`), `@omerdlw/base-framework/events`, `@omerdlw/base-framework/hooks`, `@omerdlw/base-framework/utils`, `@omerdlw/base-framework/tokens`, `@omerdlw/base-framework/atoms`, `@omerdlw/base-framework/theme`, `next/navigation`, `motion/react`.
|
|
732
|
+
- **Peers:** `loading` (page-loading flag, `stopLoading` after navigation), `media` (session state and controls), `notification` (is a toast visible). Each is read through `definePeer` with an inert stand-in, and listed in `uses`.
|
|
733
|
+
- **Used by:** `src/features/**` (surfaces, statuses, context actions, motion presets), `src/app/**` (`usePage({ dock })`, `registry.tsx`), `controls` and `notification` (they locate the dock by DOM id `dock-card-stack`), and the `context-menu` (reads the active card through `useModuleState("dock", …)`).
|
|
734
|
+
- **Never imports** another module's source (enforced by `npm run check:architecture`).
|