@tenphi/tasty 0.6.0 → 0.7.1

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/docs/debug.md ADDED
@@ -0,0 +1,505 @@
1
+ # Tasty Debug Utilities
2
+
3
+ Runtime CSS inspection and diagnostics for the Tasty styling system. Inspect injected styles, measure cache performance, analyze style chunks, and troubleshoot CSS issues — all from the browser console.
4
+
5
+ ---
6
+
7
+ ## Overview
8
+
9
+ `tastyDebug` is a diagnostic object that exposes every aspect of Tasty's runtime CSS state. It is designed for development use: inspecting which CSS is active, what's cached, how chunks are distributed, and whether cleanup is working as expected.
10
+
11
+ In development mode (`isDevEnv()` returns `true`), `tastyDebug` is automatically installed on `window.tastyDebug`. In production, you can install it manually when needed.
12
+
13
+ > **Note:** This is a development/debugging tool. It does not affect style generation or application behavior.
14
+
15
+ ---
16
+
17
+ ## Quick Start
18
+
19
+ ```typescript
20
+ // Auto-installed in dev mode. Otherwise:
21
+ import { tastyDebug } from '@tenphi/tasty';
22
+ tastyDebug.install();
23
+
24
+ // Print a quick-start guide
25
+ tastyDebug.help();
26
+
27
+ // Get a comprehensive overview logged to the console
28
+ tastyDebug.summary({ log: true });
29
+
30
+ // See all active CSS (for components currently in the DOM)
31
+ tastyDebug.log('active');
32
+
33
+ // Inspect a specific element
34
+ tastyDebug.inspect('.my-button');
35
+
36
+ // Check cache hit rates
37
+ tastyDebug.metrics();
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Core Types
43
+
44
+ ### `CSSTarget`
45
+
46
+ The `target` parameter accepted by `css()`, `log()`, and related methods:
47
+
48
+ ```typescript
49
+ type CSSTarget =
50
+ | 'all' // All tasty CSS (component + global + raw)
51
+ | 'global' // Only global CSS (from injectGlobal)
52
+ | 'active' // CSS for classes currently present in the DOM
53
+ | 'unused' // CSS with refCount = 0 (cached but not actively used)
54
+ | 'page' // ALL CSS on the page, including non-tasty stylesheets
55
+ | string // A tasty class ('t123') or a CSS selector ('.my-class')
56
+ | string[] // Array of tasty classes (['t0', 't5', 't12'])
57
+ | Element; // A DOM element
58
+ ```
59
+
60
+ ### `CssOptions`
61
+
62
+ Common options for CSS retrieval methods:
63
+
64
+ ```typescript
65
+ interface CssOptions {
66
+ root?: Document | ShadowRoot; // Target root (default: document)
67
+ prettify?: boolean; // Format CSS output (default: true)
68
+ log?: boolean; // Auto-log to console (default: false)
69
+ }
70
+ ```
71
+
72
+ ---
73
+
74
+ ## API Reference
75
+
76
+ ### `css(target, opts?): string`
77
+
78
+ Retrieves CSS text for a given target. This is the primary method for extracting CSS from the runtime.
79
+
80
+ ```typescript
81
+ // All tasty CSS
82
+ tastyDebug.css('all');
83
+
84
+ // Only CSS for classes currently in the DOM
85
+ tastyDebug.css('active');
86
+
87
+ // CSS for unused classes (refCount = 0, still in cache)
88
+ tastyDebug.css('unused');
89
+
90
+ // Only global CSS (injected via injectGlobal)
91
+ tastyDebug.css('global');
92
+
93
+ // ALL CSS on the page (including non-tasty stylesheets)
94
+ tastyDebug.css('page');
95
+
96
+ // CSS for a specific tasty class
97
+ tastyDebug.css('t42');
98
+
99
+ // CSS for multiple tasty classes
100
+ tastyDebug.css(['t0', 't5', 't12']);
101
+
102
+ // CSS affecting a DOM element (by selector)
103
+ tastyDebug.css('.my-button');
104
+
105
+ // CSS affecting a DOM element (by reference)
106
+ const el = document.querySelector('.my-button');
107
+ tastyDebug.css(el);
108
+
109
+ // With options
110
+ tastyDebug.css('active', { prettify: false, log: true });
111
+
112
+ // Shadow DOM
113
+ const shadowRoot = host.shadowRoot;
114
+ tastyDebug.css('all', { root: shadowRoot });
115
+ ```
116
+
117
+ ---
118
+
119
+ ### `inspect(target, opts?): InspectResult`
120
+
121
+ Inspects a DOM element and returns detailed information about its tasty styles, including chunk assignments.
122
+
123
+ ```typescript
124
+ interface InspectResult {
125
+ element?: Element | null;
126
+ classes: string[]; // Tasty classes found on the element (e.g., ['t0', 't5'])
127
+ chunks: ChunkInfo[]; // Chunk assignment per class
128
+ css: string; // Prettified CSS affecting the element
129
+ size: number; // CSS size in characters
130
+ rules: number; // Number of CSS rule blocks
131
+ }
132
+
133
+ interface ChunkInfo {
134
+ className: string; // e.g., 't0'
135
+ chunkName: string | null; // e.g., 'appearance', 'font', 'dimension'
136
+ }
137
+ ```
138
+
139
+ ```typescript
140
+ // By CSS selector
141
+ const result = tastyDebug.inspect('.my-card');
142
+ console.log(result.classes); // ['t3', 't7', 't12']
143
+ console.log(result.chunks); // [{className: 't3', chunkName: 'appearance'}, ...]
144
+ console.log(result.rules); // 5
145
+ console.log(result.css); // prettified CSS
146
+
147
+ // By element reference
148
+ const el = document.querySelector('[data-element="Title"]');
149
+ tastyDebug.inspect(el);
150
+
151
+ // Shadow DOM
152
+ tastyDebug.inspect('.shadow-component', { root: shadowRoot });
153
+ ```
154
+
155
+ ---
156
+
157
+ ### `cache(opts?): CacheStatus`
158
+
159
+ Returns the current state of the style cache: which classes are active, which are unused, and performance metrics.
160
+
161
+ ```typescript
162
+ interface CacheStatus {
163
+ classes: {
164
+ active: string[]; // Classes with refCount > 0 and present in DOM
165
+ unused: string[]; // Classes with refCount = 0 but still in cache
166
+ all: string[]; // Union of active and unused
167
+ };
168
+ metrics: CacheMetrics | null;
169
+ }
170
+ ```
171
+
172
+ ```typescript
173
+ const status = tastyDebug.cache();
174
+
175
+ console.log(status.classes.active.length); // 42
176
+ console.log(status.classes.unused.length); // 8
177
+ console.log(status.metrics?.hits); // 156
178
+ ```
179
+
180
+ ---
181
+
182
+ ### `cleanup(opts?): void`
183
+
184
+ Forces immediate cleanup of all unused styles (those with `refCount = 0`).
185
+
186
+ ```typescript
187
+ tastyDebug.cleanup();
188
+
189
+ // Shadow DOM
190
+ tastyDebug.cleanup({ root: shadowRoot });
191
+ ```
192
+
193
+ ---
194
+
195
+ ### `metrics(opts?): CacheMetrics | null`
196
+
197
+ Returns performance metrics for the style cache. Only available when `devMode` is enabled.
198
+
199
+ ```typescript
200
+ interface CacheMetrics {
201
+ hits: number; // Successful cache hits
202
+ misses: number; // New styles injected (cache misses)
203
+ bulkCleanups: number; // Number of bulk cleanup operations
204
+ totalInsertions: number; // Lifetime style insertions
205
+ totalUnused: number; // Total styles marked as unused
206
+ stylesCleanedUp: number; // Total styles removed by cleanup
207
+ startTime: number; // Metrics collection start timestamp
208
+ unusedHits?: number; // Reactivations of cached unused styles
209
+ cleanupHistory: {
210
+ timestamp: number;
211
+ classesDeleted: number;
212
+ cssSize: number;
213
+ rulesDeleted: number;
214
+ }[];
215
+ }
216
+ ```
217
+
218
+ ```typescript
219
+ const m = tastyDebug.metrics();
220
+
221
+ if (m) {
222
+ const hitRate = ((m.hits + (m.unusedHits || 0)) / (m.hits + m.misses)) * 100;
223
+ console.log(`Cache hit rate: ${hitRate.toFixed(1)}%`);
224
+ console.log(`Total insertions: ${m.totalInsertions}`);
225
+ console.log(`Bulk cleanups: ${m.bulkCleanups}`);
226
+ }
227
+ ```
228
+
229
+ ### `resetMetrics(opts?): void`
230
+
231
+ Resets all performance metrics counters.
232
+
233
+ ```typescript
234
+ tastyDebug.resetMetrics();
235
+ ```
236
+
237
+ ---
238
+
239
+ ### `chunks(opts?): ChunkBreakdown`
240
+
241
+ Returns a breakdown of styles by chunk type. With style chunking enabled, styles are split into logical chunks (appearance, font, dimension, etc.) for better caching and CSS reuse.
242
+
243
+ ```typescript
244
+ const breakdown = tastyDebug.chunks();
245
+
246
+ console.log(breakdown.totalChunkTypes); // 6
247
+ console.log(breakdown.totalClasses); // 87
248
+
249
+ for (const [chunkName, data] of Object.entries(breakdown.byChunk)) {
250
+ console.log(`${chunkName}: ${data.classes.length} classes, ${data.cssSize}B`);
251
+ }
252
+
253
+ // Log a formatted breakdown to the console
254
+ tastyDebug.chunks({ log: true });
255
+ ```
256
+
257
+ Chunk types: `combined` (non-chunked), `appearance`, `font`, `dimension`, `display`, `layout`, `position`, `misc`, `subcomponents`.
258
+
259
+ ---
260
+
261
+ ### `getGlobalTypeCSS(type, opts?): { css: string; ruleCount: number; size: number }`
262
+
263
+ Retrieves CSS for a specific global injection type.
264
+
265
+ ```typescript
266
+ // CSS injected via injectGlobal()
267
+ const global = tastyDebug.getGlobalTypeCSS('global');
268
+
269
+ // CSS injected via injectRawCSS() / useRawCSS()
270
+ const raw = tastyDebug.getGlobalTypeCSS('raw');
271
+
272
+ // @keyframes CSS
273
+ const keyframes = tastyDebug.getGlobalTypeCSS('keyframes');
274
+
275
+ // @property CSS
276
+ const properties = tastyDebug.getGlobalTypeCSS('property');
277
+
278
+ console.log(`Global: ${global.ruleCount} rules, ${global.size}B`);
279
+ console.log(`Raw: ${raw.ruleCount} rules, ${raw.size}B`);
280
+ console.log(`Keyframes: ${keyframes.ruleCount} rules, ${keyframes.size}B`);
281
+ console.log(`Properties: ${properties.ruleCount} rules, ${properties.size}B`);
282
+ ```
283
+
284
+ ---
285
+
286
+ ### `defs(opts?): Definitions`
287
+
288
+ Returns all defined `@property` and `@keyframes` entries.
289
+
290
+ ```typescript
291
+ interface Definitions {
292
+ properties: string[]; // Defined via @property (e.g., ['--primary-color', '--gap'])
293
+ keyframes: { name: string; refCount: number }[];
294
+ }
295
+ ```
296
+
297
+ ```typescript
298
+ const defs = tastyDebug.defs();
299
+
300
+ console.log('Properties:', defs.properties);
301
+ // ['--primary-color', '--surface-color', ...]
302
+
303
+ console.log('Keyframes:', defs.keyframes);
304
+ // [{ name: 'fadeIn', refCount: 2 }, { name: 'pulse', refCount: 1 }]
305
+ ```
306
+
307
+ ---
308
+
309
+ ### `summary(opts?): Summary`
310
+
311
+ One-shot comprehensive overview of the entire Tasty CSS state. Returns detailed statistics and optionally logs a formatted report.
312
+
313
+ ```typescript
314
+ interface SummaryOptions {
315
+ root?: Document | ShadowRoot;
316
+ log?: boolean;
317
+ includePageCSS?:
318
+ | false // Do not include page-level CSS stats (default)
319
+ | true // Include sizes/counts only
320
+ | 'all'; // Include stats and return full page CSS string
321
+ }
322
+ ```
323
+
324
+ The returned `Summary` object contains:
325
+
326
+ - **Classes**: `activeClasses`, `unusedClasses`, `totalStyledClasses`
327
+ - **CSS sizes**: `activeCSSSize`, `unusedCSSSize`, `globalCSSSize`, `rawCSSSize`, `keyframesCSSSize`, `propertyCSSSize`, `totalCSSSize`
328
+ - **CSS payloads**: `activeCSS`, `unusedCSS`, `globalCSS`, `rawCSS`, `keyframesCSS`, `propertyCSS`, `allCSS`
329
+ - **Rule counts**: `globalRuleCount`, `rawRuleCount`, `keyframesRuleCount`, `propertyRuleCount`
330
+ - **Page CSS** (when `includePageCSS` is set): `page.cssSize`, `page.ruleCount`, `page.stylesheetCount`, `page.skippedStylesheets`
331
+ - **Metrics & definitions**: `metrics`, `definedProperties`, `definedKeyframes`, `propertyCount`, `keyframeCount`
332
+ - **Cleanup summary**: `cleanupSummary.totalCleanups`, `cleanupSummary.totalClassesDeleted`, `cleanupSummary.lastCleanup`, etc.
333
+ - **Chunk breakdown**: `chunkBreakdown.byChunk`, `chunkBreakdown.totalChunkTypes`
334
+
335
+ ```typescript
336
+ // Log a formatted report
337
+ tastyDebug.summary({ log: true });
338
+
339
+ // Get the data programmatically
340
+ const s = tastyDebug.summary();
341
+ console.log(`Active: ${s.activeClasses.length} classes, ${s.activeCSSSize}B`);
342
+ console.log(`Unused: ${s.unusedClasses.length} classes, ${s.unusedCSSSize}B`);
343
+ console.log(`Total CSS: ${s.totalCSSSize}B`);
344
+
345
+ // Include page-level CSS stats
346
+ const withPage = tastyDebug.summary({ includePageCSS: true });
347
+ console.log(`Page CSS: ${withPage.page?.cssSize}B across ${withPage.page?.stylesheetCount} stylesheets`);
348
+ ```
349
+
350
+ ---
351
+
352
+ ### `pageCSS(opts?): string`
353
+
354
+ Returns all CSS on the page across all stylesheets (not only tasty-generated CSS).
355
+
356
+ ```typescript
357
+ // Get all page CSS
358
+ const css = tastyDebug.pageCSS();
359
+
360
+ // Log it
361
+ tastyDebug.pageCSS({ log: true });
362
+
363
+ // Raw (unformatted)
364
+ tastyDebug.pageCSS({ prettify: false });
365
+
366
+ // Exclude cross-origin stylesheet placeholders
367
+ tastyDebug.pageCSS({ includeCrossOrigin: false });
368
+ ```
369
+
370
+ ### `pageStats(opts?): PageStats`
371
+
372
+ Returns size and rule count statistics for all page CSS without extracting the full text.
373
+
374
+ ```typescript
375
+ const stats = tastyDebug.pageStats();
376
+ console.log(`${stats.cssSize}B across ${stats.stylesheetCount} stylesheets`);
377
+ console.log(`${stats.ruleCount} rules, ${stats.skippedStylesheets} skipped (CORS)`);
378
+ ```
379
+
380
+ ---
381
+
382
+ ### `log(target, opts?): void`
383
+
384
+ Logs CSS for a target to the console with formatted output, collapsible groups, and sub-element detection.
385
+
386
+ ```typescript
387
+ // Log active CSS with stats header
388
+ tastyDebug.log('active');
389
+
390
+ // Log CSS for a specific class
391
+ tastyDebug.log('t42');
392
+
393
+ // Log with custom title
394
+ tastyDebug.log('active', { title: 'Button styles' });
395
+
396
+ // Log CSS for an element
397
+ tastyDebug.log('.my-card');
398
+ ```
399
+
400
+ The output includes:
401
+ - Rule count, line count, and byte size
402
+ - Sub-element breakdown (detects `[data-element="..."]` selectors)
403
+ - Full CSS in a collapsible group
404
+
405
+ ---
406
+
407
+ ### `help(): void`
408
+
409
+ Prints a quick-start guide to the console with available commands and common targets.
410
+
411
+ ```typescript
412
+ tastyDebug.help();
413
+ ```
414
+
415
+ ---
416
+
417
+ ### `install(): void`
418
+
419
+ Attaches `tastyDebug` to `window.tastyDebug` for console access. Called automatically in development mode.
420
+
421
+ ```typescript
422
+ import { tastyDebug } from '@tenphi/tasty';
423
+
424
+ // Manual install (e.g., in staging/production for debugging)
425
+ tastyDebug.install();
426
+
427
+ // Then use from the browser console:
428
+ // > tastyDebug.summary({ log: true })
429
+ ```
430
+
431
+ ---
432
+
433
+ ## Common Workflows
434
+
435
+ ### Debugging a component's styles
436
+
437
+ ```typescript
438
+ // 1. Find the element
439
+ tastyDebug.inspect('.my-button');
440
+ // → { classes: ['t3', 't7'], chunks: [...], css: '...', rules: 4 }
441
+
442
+ // 2. See CSS for a specific class
443
+ tastyDebug.log('t3');
444
+
445
+ // 3. Check which chunk it belongs to
446
+ tastyDebug.inspect('.my-button').chunks;
447
+ // → [{ className: 't3', chunkName: 'appearance' }, { className: 't7', chunkName: 'font' }]
448
+ ```
449
+
450
+ ### Checking cache efficiency
451
+
452
+ ```typescript
453
+ const m = tastyDebug.metrics();
454
+ const hitRate = m ? ((m.hits + (m.unusedHits || 0)) / (m.hits + m.misses)) * 100 : 0;
455
+ console.log(`Hit rate: ${hitRate.toFixed(1)}%`);
456
+ console.log(`Unused style reactivations: ${m?.unusedHits}`);
457
+ ```
458
+
459
+ ### Monitoring CSS growth
460
+
461
+ ```typescript
462
+ const s = tastyDebug.summary();
463
+ console.log(`Total tasty CSS: ${(s.totalCSSSize / 1024).toFixed(1)}KB`);
464
+ console.log(`Active: ${(s.activeCSSSize / 1024).toFixed(1)}KB`);
465
+ console.log(`Unused (pending cleanup): ${(s.unusedCSSSize / 1024).toFixed(1)}KB`);
466
+
467
+ // Compare with total page CSS
468
+ const page = tastyDebug.pageStats();
469
+ const ratio = ((s.totalCSSSize / page.cssSize) * 100).toFixed(1);
470
+ console.log(`Tasty is ${ratio}% of total page CSS`);
471
+ ```
472
+
473
+ ### Analyzing chunk distribution
474
+
475
+ ```typescript
476
+ tastyDebug.chunks({ log: true });
477
+ // → Formatted breakdown:
478
+ // • appearance: 24 classes, 3.2KB, 48 rules
479
+ // • font: 18 classes, 1.1KB, 18 rules
480
+ // • dimension: 31 classes, 2.4KB, 45 rules
481
+ // • ...
482
+ ```
483
+
484
+ ---
485
+
486
+ ## Shadow DOM Support
487
+
488
+ All methods accept a `root` option to target a Shadow DOM instead of the main document:
489
+
490
+ ```typescript
491
+ const shadowRoot = host.shadowRoot;
492
+
493
+ tastyDebug.css('all', { root: shadowRoot });
494
+ tastyDebug.inspect('.shadow-component', { root: shadowRoot });
495
+ tastyDebug.summary({ root: shadowRoot, log: true });
496
+ tastyDebug.chunks({ root: shadowRoot, log: true });
497
+ ```
498
+
499
+ ---
500
+
501
+ ## Integration with Tasty
502
+
503
+ `tastyDebug` reads directly from the [Style Injector](./injector.md)'s internal registries. It does not inject, modify, or intercept any styles. The `cleanup()` method is the only method with side effects — it triggers the injector's garbage collection for unused styles.
504
+
505
+ For most development, you'll use the [Tasty style system](./usage.md) to create components and the debug utilities to inspect the resulting CSS at runtime.