@marsaude/devtools-shell 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,249 @@
1
+ # @marsaude/devtools-shell
2
+
3
+ A **content-agnostic, draggable floating-action-button shell** for dev-only tools.
4
+
5
+ It ships only the _invólucro_: a FAB you can drag anywhere on screen (mouse +
6
+ touch, position persisted across reloads, clamped to the viewport) that opens a
7
+ container. **The panel content is yours** — plugged in from the outside. The
8
+ shell has zero knowledge of auth, APIs, or any business domain.
9
+
10
+ - Angular standalone + Signals + `@if`/`@for` control flow. No NgModules.
11
+ - Drag/animations ported verbatim from the `DevTools FAB` prototype — plain
12
+ Pointer Events, no external drag library.
13
+ - Auto-mounts itself (`createComponent + ApplicationRef.attachView` from an
14
+ `APP_BOOTSTRAP_LISTENER`) — no tag to place in a template.
15
+
16
+ **Shell behaviour (all domain-free):** drag + snap-to-edge + persisted position;
17
+ idle → collapse into a thin edge grip (tap to restore); tap → radial speed-dial
18
+ of the registered actions (or open directly when there's a single action); pick
19
+ an action → its content renders in a side-drawer / bottom-sheet.
20
+
21
+ **Toast:** inject `DevtoolsToastService` anywhere and call `show('…')` to flash a
22
+ message inside the shell layer (no Material dependency):
23
+
24
+ ```ts
25
+ import { DevtoolsToastService } from '@marsaude/devtools-shell';
26
+ private readonly toast = inject(DevtoolsToastService);
27
+ this.toast.show('Usuário gerado');
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ npm i @marsaude/devtools-shell
36
+ # peers (already present in this workspace):
37
+ npm i @angular/core @angular/common
38
+ ```
39
+
40
+ Material Symbols are used for glyphs. Load them once in the host app if you want
41
+ the icons to render:
42
+
43
+ ```html
44
+ <link
45
+ href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined"
46
+ rel="stylesheet"
47
+ />
48
+ ```
49
+
50
+ ## Mount the shell
51
+
52
+ Add `provideDevtools()` to your application providers.
53
+
54
+ ### Standalone bootstrap
55
+
56
+ ```ts
57
+ import { bootstrapApplication } from '@angular/platform-browser';
58
+ import { provideDevtools } from '@marsaude/devtools-shell';
59
+ import { environment } from './environments/environment';
60
+
61
+ bootstrapApplication(AppComponent, {
62
+ providers: [
63
+ provideDevtools({
64
+ enabled: !environment.production, // Layer-1 gate (see below)
65
+ title: 'DevTools',
66
+ actions: [],
67
+ }),
68
+ ],
69
+ });
70
+ ```
71
+
72
+ ### NgModule app (this repo)
73
+
74
+ `provideDevtools()` returns `EnvironmentProviders`, valid in `@NgModule.providers`:
75
+
76
+ ```ts
77
+ @NgModule({
78
+ // ...
79
+ providers: [
80
+ provideDevtools({ enabled: !environment.production }),
81
+ ],
82
+ })
83
+ export class AppModule {}
84
+ ```
85
+
86
+ That's all — a draggable FAB appears, and clicking it opens an (empty) panel.
87
+
88
+ ## Pass the interface later (the extension point)
89
+
90
+ The panel content is supplied as **actions**. Each action is a standalone
91
+ component (or a `TemplateRef`) — the shell just renders it.
92
+
93
+ ```ts
94
+ import { provideDevtools } from '@marsaude/devtools-shell';
95
+ import { MyUserGeneratorPanel } from './devtools/user-generator-panel';
96
+
97
+ provideDevtools({
98
+ enabled: !environment.production,
99
+ actions: [
100
+ { id: 'gen', label: 'Gerador', icon: 'groups', content: MyUserGeneratorPanel },
101
+ { id: 'login', label: 'Logar', icon: 'login', content: MyLoginPanel },
102
+ ],
103
+ });
104
+ ```
105
+
106
+ - **One action** → it opens directly in the container.
107
+ - **Multiple actions** → the shell renders a tab switcher in the panel header.
108
+ - Your panel component is rendered with `NgComponentOutlet`; it can inject its
109
+ own services normally.
110
+
111
+ ### Two doors for content
112
+
113
+ 1. **Action registry (primary).** Shown above. This is the recommended path
114
+ because the shell auto-mounts onto `document.body` — it has no place in your
115
+ template, so there is nowhere to project into.
116
+ 2. **Content projection (secondary).** Only when you place the component
117
+ yourself instead of using `provideDevtools` auto-mount:
118
+
119
+ ```html
120
+ <devtools-shell>
121
+ <my-panel />
122
+ </devtools-shell>
123
+ ```
124
+
125
+ `<ng-content>` is rendered when no actions are registered.
126
+
127
+ > **Why the registry is primary:** auto-mounting via `createComponent` (the
128
+ > pattern reused from the original DevTools boot flow) means the shell lives
129
+ > outside any consumer template. `<ng-content>` requires the consumer to host
130
+ > the tag, which contradicts auto-mount. The token-based registry decouples
131
+ > _where the shell lives_ from _who supplies the content_.
132
+
133
+ ## Re-skinning
134
+
135
+ The shell ships the dark theme from the original mockup, exposed as CSS custom
136
+ properties on the host element. Override them anywhere:
137
+
138
+ ```css
139
+ [data-devtools-shell-host] devtools-shell {
140
+ --dts-bg: #14151b;
141
+ --dts-accent: #ffc454;
142
+ --dts-fg: #eceef3;
143
+ --dts-font: system-ui, sans-serif;
144
+ }
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Production gating
150
+
151
+ **The shell must never reach a production bundle.** Two layers:
152
+
153
+ ### Layer 1 — runtime flag (always on)
154
+
155
+ Pass `enabled: !environment.production`. When `false`, `provideDevtools()`
156
+ returns _no_ providers: nothing mounts, no actions register.
157
+
158
+ ### Layer 2 — build-time elimination (recommended)
159
+
160
+ Layer 1 still leaves the shell _imported_. To drop it from the bundle entirely,
161
+ isolate the wiring in one file and swap it via `fileReplacements`.
162
+
163
+ `src/app/devtools/devtools.providers.ts` (dev):
164
+
165
+ ```ts
166
+ import { EnvironmentProviders } from '@angular/core';
167
+ import { provideDevtools } from '@marsaude/devtools-shell';
168
+ import { UserGeneratorPanel } from './user-generator-panel';
169
+
170
+ export function devtoolsProviders(): EnvironmentProviders[] {
171
+ return [
172
+ provideDevtools({
173
+ enabled: true,
174
+ actions: [{ id: 'gen', label: 'Gerador', icon: 'groups', content: UserGeneratorPanel }],
175
+ }),
176
+ ];
177
+ }
178
+ ```
179
+
180
+ `src/app/devtools/devtools.providers.prod.ts` (prod no-op — imports nothing):
181
+
182
+ ```ts
183
+ export function devtoolsProviders() {
184
+ return [];
185
+ }
186
+ ```
187
+
188
+ `angular.json` (production configuration):
189
+
190
+ ```jsonc
191
+ "fileReplacements": [
192
+ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.prod.ts" },
193
+ { "replace": "src/app/devtools/devtools.providers.ts", "with": "src/app/devtools/devtools.providers.prod.ts" }
194
+ ]
195
+ ```
196
+
197
+ Use it in bootstrap:
198
+
199
+ ```ts
200
+ import { devtoolsProviders } from './app/devtools/devtools.providers';
201
+ // providers: [ ...devtoolsProviders() ]
202
+ ```
203
+
204
+ Because the prod file imports neither `@marsaude/devtools-shell` nor your panel
205
+ components, the bundler tree-shakes the whole shell out of the production build.
206
+
207
+ ### Verification checklist
208
+
209
+ - [ ] `provideDevtools({ enabled: !environment.production })` — Layer 1 in place.
210
+ - [ ] Wiring isolated in `devtools.providers.ts` with a `.prod.ts` no-op twin.
211
+ - [ ] `fileReplacements` entry for the providers file added to the **production**
212
+ configuration in `angular.json`.
213
+ - [ ] `environment.prod.ts` actually has `production: true` for the real prod env.
214
+ - [ ] Run a prod build and confirm the shell is gone:
215
+ `ng build --configuration production` then
216
+ `grep -r "devtools-shell\|data-devtools-shell-host" dist/` returns nothing.
217
+ - [ ] Load the prod bundle: no FAB on screen, no `[data-devtools-shell-host]`
218
+ element in the DOM.
219
+
220
+ ---
221
+
222
+ ## Build & publish (public npm — scope `@marsaude`)
223
+
224
+ Published as a **public** scoped package on npmjs (free). You must be logged in
225
+ as a user that owns the `@marsaude` scope.
226
+
227
+ ```bash
228
+ # one-time: authenticate against npmjs
229
+ npm login # npm whoami → marsaude
230
+
231
+ # build + publish in one step (from the workspace root)
232
+ npm run publish:lib
233
+ # → runs `ng build devtools-shell` then
234
+ # `npm publish ./dist/devtools-shell --access public`
235
+ ```
236
+
237
+ Manual equivalent:
238
+
239
+ ```bash
240
+ npm run build:lib # → dist/devtools-shell
241
+ npm publish ./dist/devtools-shell --access public # note the ./ prefix
242
+ ```
243
+
244
+ Bump the version in `projects/devtools-shell/package.json` before each release
245
+ (`0.1.0` → `0.1.1` …). `peerDependencies`: `@angular/core`, `@angular/common`
246
+ (^21).
247
+
248
+ > `--access public` is required for the first publish of a scoped package on the
249
+ > free npm plan (private/restricted needs a paid npm plan → `E402`).