@fgv/ts-app-shell 5.1.0-31 → 5.1.0-33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +23 -0
  2. package/dist/packlets/messages/MessagesContext.js +79 -3
  3. package/dist/packlets/messages/MessagesContext.js.map +1 -1
  4. package/dist/packlets/messages/MessagesLogger.js +10 -31
  5. package/dist/packlets/messages/MessagesLogger.js.map +1 -1
  6. package/dist/packlets/messages/StatusBar.js +53 -28
  7. package/dist/packlets/messages/StatusBar.js.map +1 -1
  8. package/dist/packlets/messages/Toast.js +7 -4
  9. package/dist/packlets/messages/Toast.js.map +1 -1
  10. package/dist/packlets/messages/index.js +2 -2
  11. package/dist/packlets/messages/index.js.map +1 -1
  12. package/dist/packlets/messages/model.js +31 -4
  13. package/dist/packlets/messages/model.js.map +1 -1
  14. package/lib/packlets/messages/MessagesContext.d.ts +47 -3
  15. package/lib/packlets/messages/MessagesContext.d.ts.map +1 -1
  16. package/lib/packlets/messages/MessagesContext.js +79 -2
  17. package/lib/packlets/messages/MessagesContext.js.map +1 -1
  18. package/lib/packlets/messages/MessagesLogger.d.ts +8 -3
  19. package/lib/packlets/messages/MessagesLogger.d.ts.map +1 -1
  20. package/lib/packlets/messages/MessagesLogger.js +10 -31
  21. package/lib/packlets/messages/MessagesLogger.js.map +1 -1
  22. package/lib/packlets/messages/StatusBar.d.ts +10 -1
  23. package/lib/packlets/messages/StatusBar.d.ts.map +1 -1
  24. package/lib/packlets/messages/StatusBar.js +52 -27
  25. package/lib/packlets/messages/StatusBar.js.map +1 -1
  26. package/lib/packlets/messages/Toast.d.ts.map +1 -1
  27. package/lib/packlets/messages/Toast.js +6 -3
  28. package/lib/packlets/messages/Toast.js.map +1 -1
  29. package/lib/packlets/messages/index.d.ts +2 -2
  30. package/lib/packlets/messages/index.d.ts.map +1 -1
  31. package/lib/packlets/messages/index.js +6 -1
  32. package/lib/packlets/messages/index.js.map +1 -1
  33. package/lib/packlets/messages/model.d.ts +74 -8
  34. package/lib/packlets/messages/model.d.ts.map +1 -1
  35. package/lib/packlets/messages/model.js +32 -4
  36. package/lib/packlets/messages/model.js.map +1 -1
  37. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"Toast.js","sourceRoot":"","sources":["../../../src/packlets/messages/Toast.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEzC,OAAO,EAAE,oBAAoB,EAA6B,MAAM,SAAS,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,+EAA+E;AAC/E,kBAAkB;AAClB,+EAA+E;AAE/E,MAAM,eAAe,GAAoC;IACvD,IAAI,EAAE,mEAAmE;IACzE,OAAO,EAAE,4EAA4E;IACrF,OAAO,EAAE,4EAA4E;IACrF,KAAK,EAAE,sEAAsE;CAC9E,CAAC;AAeF;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,KAAsB;IAC9C,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;IACrC,MAAM,MAAM,GAAG,oBAAoB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACtD,MAAM,EAAE,UAAU,EAAE,GAAG,aAAa,EAAE,CAAC;IAEvC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,MAAM,CAAC,aAAa,GAAG,CAAC,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC;YAC5E,OAAO,GAAS,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QACzC,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC;IAElD,OAAO,CACL,6BACE,SAAS,EAAE,gEACT,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UACvC,IAAI,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EACvC,IAAI,EAAC,OAAO;QAEZ,6BAAK,SAAS,EAAC,6BAA6B,EAAC,KAAK,EAAE,OAAO,CAAC,IAAI,IAC7D,OAAO,CAAC,IAAI,CACT;QACN,6BAAK,SAAS,EAAC,kCAAkC;YAC9C,OAAO,CAAC,MAAM,IAAI,CACjB,gCACE,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,EAChC,SAAS,EAAC,kDAAkD,IAE3D,OAAO,CAAC,MAAM,CAAC,KAAK,CACd,CACV;YACD,gCACE,OAAO,EAAE,GAAS,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,EAC1C,SAAS,EAAC,2CAA2C,gBAC1C,SAAS,aAGb,CACL,CACF,CACP,CAAC;AACJ,CAAC;AAiBD;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,KAA2B;IACxD,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC;IACpD,MAAM,EAAE,UAAU,EAAE,GAAG,aAAa,EAAE,CAAC;IACvC,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC;IAE1C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,CACL,6BACE,SAAS,EAAE,4CACT,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,SAC/C,EAAE,IAED,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CACtB,oBAAC,SAAS,IAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,GAAI,CACnE,CAAC,CACE,CACP,CAAC;AACJ,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport React, { useEffect } from 'react';\n\nimport { DEFAULT_TOAST_CONFIG, IMessage, MessageSeverity } from './model';\nimport { useResponsive } from '../responsive';\n\n// ============================================================================\n// Severity Styles\n// ============================================================================\n\nconst SEVERITY_STYLES: Record<MessageSeverity, string> = {\n info: 'bg-status-info-bg border-status-info-border text-status-info-text',\n success: 'bg-status-success-bg border-status-success-border text-status-success-text',\n warning: 'bg-status-warning-bg border-status-warning-border text-status-warning-text',\n error: 'bg-status-error-bg border-status-error-border text-status-error-text'\n};\n\n// ============================================================================\n// Single Toast\n// ============================================================================\n\n/**\n * Props for a single Toast item.\n * @public\n */\nexport interface IToastItemProps {\n readonly message: IMessage;\n readonly onDismiss: (id: string) => void;\n}\n\n/**\n * A single toast notification.\n * @public\n */\nexport function ToastItem(props: IToastItemProps): React.ReactElement {\n const { message, onDismiss } = props;\n const config = DEFAULT_TOAST_CONFIG[message.severity];\n const { layoutMode } = useResponsive();\n\n useEffect(() => {\n if (config.autoDismissMs > 0) {\n const timer = setTimeout(() => onDismiss(message.id), config.autoDismissMs);\n return (): void => clearTimeout(timer);\n }\n return undefined;\n }, [message.id, config.autoDismissMs, onDismiss]);\n\n return (\n <div\n className={`flex items-start gap-3 px-4 py-3 rounded-lg border shadow-lg ${\n layoutMode === 'mobile' ? 'w-full' : 'max-w-sm'\n } ${SEVERITY_STYLES[message.severity]}`}\n role=\"alert\"\n >\n <div className=\"flex-1 text-sm line-clamp-4\" title={message.text}>\n {message.text}\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n {message.action && (\n <button\n onClick={message.action.onAction}\n className=\"text-sm font-medium underline hover:no-underline\"\n >\n {message.action.label}\n </button>\n )}\n <button\n onClick={(): void => onDismiss(message.id)}\n className=\"text-current opacity-50 hover:opacity-100\"\n aria-label=\"Dismiss\"\n >\n &times;\n </button>\n </div>\n </div>\n );\n}\n\n// ============================================================================\n// Toast Container\n// ============================================================================\n\n/**\n * Props for the ToastContainer.\n * @public\n */\nexport interface IToastContainerProps {\n readonly toasts: ReadonlyArray<IMessage>;\n readonly onDismiss: (id: string) => void;\n /** Maximum number of toasts to show simultaneously */\n readonly maxVisible?: number;\n}\n\n/**\n * Container that renders active toasts in the bottom-right corner.\n * @public\n */\nexport function ToastContainer(props: IToastContainerProps): React.ReactElement | null {\n const { toasts, onDismiss, maxVisible = 5 } = props;\n const { layoutMode } = useResponsive();\n const visible = toasts.slice(-maxVisible);\n\n if (visible.length === 0) {\n return null;\n }\n\n return (\n <div\n className={`fixed bottom-16 z-50 flex flex-col gap-2 ${\n layoutMode === 'mobile' ? 'left-4 right-4' : 'right-4'\n }`}\n >\n {visible.map((toast) => (\n <ToastItem key={toast.id} message={toast} onDismiss={onDismiss} />\n ))}\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"Toast.js","sourceRoot":"","sources":["../../../src/packlets/messages/Toast.tsx"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport React, { useEffect } from 'react';\n\nimport { DEFAULT_TOAST_CONFIG, deriveSeverityFromLevel, IMessage, MessageSeverity } from './model';\nimport { useResponsive } from '../responsive';\n\n// ============================================================================\n// Severity Styles\n// ============================================================================\n\nconst SEVERITY_STYLES: Record<MessageSeverity, string> = {\n info: 'bg-status-info-bg border-status-info-border text-status-info-text',\n success: 'bg-status-success-bg border-status-success-border text-status-success-text',\n warning: 'bg-status-warning-bg border-status-warning-border text-status-warning-text',\n error: 'bg-status-error-bg border-status-error-border text-status-error-text'\n};\n\n// ============================================================================\n// Single Toast\n// ============================================================================\n\n/**\n * Props for a single Toast item.\n * @public\n */\nexport interface IToastItemProps {\n readonly message: IMessage;\n readonly onDismiss: (id: string) => void;\n}\n\n/**\n * A single toast notification.\n * @public\n */\nexport function ToastItem(props: IToastItemProps): React.ReactElement {\n const { message, onDismiss } = props;\n const severity = message.severity ?? deriveSeverityFromLevel(message.level);\n const config = DEFAULT_TOAST_CONFIG[severity];\n const { layoutMode } = useResponsive();\n\n useEffect(() => {\n if (config.autoDismissMs > 0) {\n const timer = setTimeout(() => onDismiss(message.id), config.autoDismissMs);\n return (): void => clearTimeout(timer);\n }\n return undefined;\n }, [message.id, config.autoDismissMs, onDismiss]);\n\n return (\n <div\n className={`flex items-start gap-3 px-4 py-3 rounded-lg border shadow-lg ${\n layoutMode === 'mobile' ? 'w-full' : 'max-w-sm'\n } ${SEVERITY_STYLES[severity]}`}\n role=\"alert\"\n >\n <div className=\"flex-1 text-sm line-clamp-4\" title={message.text}>\n {message.text}\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n {message.action &&\n (message.action.href !== undefined ? (\n <a\n href={message.action.href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-sm font-medium underline hover:no-underline\"\n >\n {message.action.label}\n </a>\n ) : (\n <button\n onClick={message.action.onAction}\n className=\"text-sm font-medium underline hover:no-underline\"\n >\n {message.action.label}\n </button>\n ))}\n <button\n onClick={(): void => onDismiss(message.id)}\n className=\"text-current opacity-50 hover:opacity-100\"\n aria-label=\"Dismiss\"\n >\n &times;\n </button>\n </div>\n </div>\n );\n}\n\n// ============================================================================\n// Toast Container\n// ============================================================================\n\n/**\n * Props for the ToastContainer.\n * @public\n */\nexport interface IToastContainerProps {\n readonly toasts: ReadonlyArray<IMessage>;\n readonly onDismiss: (id: string) => void;\n /** Maximum number of toasts to show simultaneously */\n readonly maxVisible?: number;\n}\n\n/**\n * Container that renders active toasts in the bottom-right corner.\n * @public\n */\nexport function ToastContainer(props: IToastContainerProps): React.ReactElement | null {\n const { toasts, onDismiss, maxVisible = 5 } = props;\n const { layoutMode } = useResponsive();\n const visible = toasts.slice(-maxVisible);\n\n if (visible.length === 0) {\n return null;\n }\n\n return (\n <div\n className={`fixed bottom-16 z-50 flex flex-col gap-2 ${\n layoutMode === 'mobile' ? 'left-4 right-4' : 'right-4'\n }`}\n >\n {visible.map((toast) => (\n <ToastItem key={toast.id} message={toast} onDismiss={onDismiss} />\n ))}\n </div>\n );\n}\n"]}
@@ -2,8 +2,8 @@
2
2
  * Messages packlet - observability context, toast system, and log panel.
3
3
  * @packageDocumentation
4
4
  */
5
- export { DEFAULT_TOAST_CONFIG, createMessage, generateMessageId } from './model';
6
- export { MessagesProvider, useMessages } from './MessagesContext';
5
+ export { DEFAULT_TOAST_CONFIG, deriveSeverityFromLevel, createMessage, generateMessageId } from './model';
6
+ export { MessagesProvider, TAILWIND_PROBE_CLASS, TAILWIND_PROBE_HEIGHT_PX, TAILWIND_PROBE_MESSAGE_TEXT, TAILWIND_SETUP_DOCS_URL, useMessages } from './MessagesContext';
7
7
  export { ToastItem, ToastContainer } from './Toast';
8
8
  export { StatusBar } from './StatusBar';
9
9
  export { MessagesLogger } from './MessagesLogger';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/messages/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAKL,oBAAoB,EACpB,aAAa,EACb,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAEjB,OAAO,EAGL,gBAAgB,EAChB,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,SAAS,EAAE,cAAc,EAAmD,MAAM,SAAS,CAAC;AAErG,OAAO,EAAE,SAAS,EAAwB,MAAM,aAAa,CAAC;AAE9D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,cAAc,EAA+B,MAAM,kBAAkB,CAAC","sourcesContent":["/**\n * Messages packlet - observability context, toast system, and log panel.\n * @packageDocumentation\n */\n\nexport {\n type MessageSeverity,\n type IMessage,\n type IMessageAction,\n type IToastConfig,\n DEFAULT_TOAST_CONFIG,\n createMessage,\n generateMessageId\n} from './model';\n\nexport {\n type IMessagesContextValue,\n type IMessagesProviderProps,\n MessagesProvider,\n useMessages\n} from './MessagesContext';\n\nexport { ToastItem, ToastContainer, type IToastItemProps, type IToastContainerProps } from './Toast';\n\nexport { StatusBar, type IStatusBarProps } from './StatusBar';\n\nexport { MessagesLogger } from './MessagesLogger';\n\nexport { useLogReporter, type IUseLogReporterOptions } from './useLogReporter';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/messages/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAQL,oBAAoB,EACpB,uBAAuB,EACvB,aAAa,EACb,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAEjB,OAAO,EAGL,gBAAgB,EAChB,oBAAoB,EACpB,wBAAwB,EACxB,2BAA2B,EAC3B,uBAAuB,EACvB,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,SAAS,EAAE,cAAc,EAAmD,MAAM,SAAS,CAAC;AAErG,OAAO,EAAE,SAAS,EAAwB,MAAM,aAAa,CAAC;AAE9D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,cAAc,EAA+B,MAAM,kBAAkB,CAAC","sourcesContent":["/**\n * Messages packlet - observability context, toast system, and log panel.\n * @packageDocumentation\n */\n\nexport {\n type MessageSeverity,\n type IMessage,\n type IMessageAction,\n type IMessageHrefAction,\n type IMessageCallbackAction,\n type ICreateMessageOptions,\n type IToastConfig,\n DEFAULT_TOAST_CONFIG,\n deriveSeverityFromLevel,\n createMessage,\n generateMessageId\n} from './model';\n\nexport {\n type IMessagesContextValue,\n type IMessagesProviderProps,\n MessagesProvider,\n TAILWIND_PROBE_CLASS,\n TAILWIND_PROBE_HEIGHT_PX,\n TAILWIND_PROBE_MESSAGE_TEXT,\n TAILWIND_SETUP_DOCS_URL,\n useMessages\n} from './MessagesContext';\n\nexport { ToastItem, ToastContainer, type IToastItemProps, type IToastContainerProps } from './Toast';\n\nexport { StatusBar, type IStatusBarProps } from './StatusBar';\n\nexport { MessagesLogger } from './MessagesLogger';\n\nexport { useLogReporter, type IUseLogReporterOptions } from './useLogReporter';\n"]}
@@ -19,6 +19,26 @@
19
19
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
20
  * SOFTWARE.
21
21
  */
22
+ /**
23
+ * Derives a display {@link MessageSeverity} from a canonical
24
+ * {@link @fgv/ts-utils#MessageLogLevel | log level}, used when a message carries no explicit
25
+ * `severity` styling override. `'success'` is never derived — it is a UI-only affordance.
26
+ * @param level - The canonical log level.
27
+ * @returns The styling severity to use for display.
28
+ * @public
29
+ */
30
+ export function deriveSeverityFromLevel(level) {
31
+ switch (level) {
32
+ case 'warning':
33
+ return 'warning';
34
+ case 'error':
35
+ return 'error';
36
+ case 'quiet':
37
+ case 'detail':
38
+ case 'info':
39
+ return 'info';
40
+ }
41
+ }
22
42
  /**
23
43
  * Default toast configuration by severity.
24
44
  * @public
@@ -41,16 +61,23 @@ export function generateMessageId() {
41
61
  return `msg-${++_nextId}-${Date.now().toString(36)}`;
42
62
  }
43
63
  /**
44
- * Creates a new message.
64
+ * Creates a new message at the given canonical {@link @fgv/ts-utils#MessageLogLevel | log level}.
65
+ * The level drives filtering; pass `options.severity` only to override display styling
66
+ * (e.g. `'success'`).
67
+ * @param level - The canonical log level (filter axis).
68
+ * @param text - The message text.
69
+ * @param options - Optional styling severity and action.
70
+ * @returns The created message.
45
71
  * @public
46
72
  */
47
- export function createMessage(severity, text, action) {
73
+ export function createMessage(level, text, options) {
48
74
  return {
49
75
  id: generateMessageId(),
50
- severity,
76
+ level,
77
+ severity: options === null || options === void 0 ? void 0 : options.severity,
51
78
  text,
52
79
  timestamp: Date.now(),
53
- action
80
+ action: options === null || options === void 0 ? void 0 : options.action
54
81
  };
55
82
  }
56
83
  //# sourceMappingURL=model.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/packlets/messages/model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AA8DH;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAA0C;IACzE,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE;IAC7B,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE;IAChC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE;IAChC,KAAK,EAAE,EAAE,aAAa,EAAE,CAAC,EAAE;CACnB,CAAC;AAEX,+EAA+E;AAC/E,UAAU;AACV,+EAA+E;AAE/E,IAAI,OAAO,GAAW,CAAC,CAAC;AAExB;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,OAAO,EAAE,OAAO,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;AACvD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,QAAyB,EAAE,IAAY,EAAE,MAAuB;IAC5F,OAAO;QACL,EAAE,EAAE,iBAAiB,EAAE;QACvB,QAAQ;QACR,IAAI;QACJ,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;QACrB,MAAM;KACP,CAAC;AACJ,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Message model types for the observability system.\n * @packageDocumentation\n */\n\n// ============================================================================\n// Message Severity\n// ============================================================================\n\n/**\n * Severity levels for messages.\n * @public\n */\nexport type MessageSeverity = 'info' | 'success' | 'warning' | 'error';\n\n// ============================================================================\n// Message\n// ============================================================================\n\n/**\n * A single message in the observability stream.\n * @public\n */\nexport interface IMessage {\n /** Unique message ID */\n readonly id: string;\n /** Message severity */\n readonly severity: MessageSeverity;\n /** Message text */\n readonly text: string;\n /** Timestamp (ms since epoch) */\n readonly timestamp: number;\n /** Optional action (e.g., a link or callback) */\n readonly action?: IMessageAction;\n}\n\n/**\n * An actionable element attached to a message (e.g., \"Go to Session\" link).\n * @public\n */\nexport interface IMessageAction {\n /** Action label */\n readonly label: string;\n /** Callback when the action is triggered */\n readonly onAction: () => void;\n}\n\n// ============================================================================\n// Toast Configuration\n// ============================================================================\n\n/**\n * Configuration for toast auto-dismiss behavior.\n * @public\n */\nexport interface IToastConfig {\n /** Auto-dismiss duration in ms. 0 = no auto-dismiss. */\n readonly autoDismissMs: number;\n}\n\n/**\n * Default toast configuration by severity.\n * @public\n */\nexport const DEFAULT_TOAST_CONFIG: Record<MessageSeverity, IToastConfig> = {\n info: { autoDismissMs: 3000 },\n success: { autoDismissMs: 3000 },\n warning: { autoDismissMs: 5000 },\n error: { autoDismissMs: 0 }\n} as const;\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nlet _nextId: number = 0;\n\n/**\n * Generates a unique message ID.\n * @internal\n */\nexport function generateMessageId(): string {\n return `msg-${++_nextId}-${Date.now().toString(36)}`;\n}\n\n/**\n * Creates a new message.\n * @public\n */\nexport function createMessage(severity: MessageSeverity, text: string, action?: IMessageAction): IMessage {\n return {\n id: generateMessageId(),\n severity,\n text,\n timestamp: Date.now(),\n action\n };\n}\n"]}
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/packlets/messages/model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAuBH;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAsB;IAC5D,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,SAAS;YACZ,OAAO,SAAS,CAAC;QACnB,KAAK,OAAO;YACV,OAAO,OAAO,CAAC;QACjB,KAAK,OAAO,CAAC;QACb,KAAK,QAAQ,CAAC;QACd,KAAK,MAAM;YACT,OAAO,MAAM,CAAC;IAClB,CAAC;AACH,CAAC;AAkFD;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAA0C;IACzE,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE;IAC7B,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE;IAChC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE;IAChC,KAAK,EAAE,EAAE,aAAa,EAAE,CAAC,EAAE;CACnB,CAAC;AAEX,+EAA+E;AAC/E,UAAU;AACV,+EAA+E;AAE/E,IAAI,OAAO,GAAW,CAAC,CAAC;AAExB;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,OAAO,EAAE,OAAO,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;AACvD,CAAC;AAgBD;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAC3B,KAAsB,EACtB,IAAY,EACZ,OAA+B;IAE/B,OAAO;QACL,EAAE,EAAE,iBAAiB,EAAE;QACvB,KAAK;QACL,QAAQ,EAAE,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,QAAQ;QAC3B,IAAI;QACJ,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;QACrB,MAAM,EAAE,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,MAAM;KACxB,CAAC;AACJ,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Message model types for the observability system.\n * @packageDocumentation\n */\n\nimport { type MessageLogLevel } from '@fgv/ts-utils';\n\n// ============================================================================\n// Message Severity\n// ============================================================================\n\n/**\n * Display styling levels for messages — drives color, icon, and toast behavior.\n *\n * This is the styling axis, orthogonal to the {@link @fgv/ts-utils#MessageLogLevel | log level}\n * filter axis on {@link IMessage.level}. `'success'` lives here (a UI affordance with no\n * log-level analog) and is only ever set explicitly — never derived from a logger bridge.\n * @public\n */\nexport type MessageSeverity = 'info' | 'success' | 'warning' | 'error';\n\n/**\n * Derives a display {@link MessageSeverity} from a canonical\n * {@link @fgv/ts-utils#MessageLogLevel | log level}, used when a message carries no explicit\n * `severity` styling override. `'success'` is never derived — it is a UI-only affordance.\n * @param level - The canonical log level.\n * @returns The styling severity to use for display.\n * @public\n */\nexport function deriveSeverityFromLevel(level: MessageLogLevel): MessageSeverity {\n switch (level) {\n case 'warning':\n return 'warning';\n case 'error':\n return 'error';\n case 'quiet':\n case 'detail':\n case 'info':\n return 'info';\n }\n}\n\n// ============================================================================\n// Message\n// ============================================================================\n\n/**\n * A single message in the observability stream.\n * @public\n */\nexport interface IMessage {\n /** Unique message ID */\n readonly id: string;\n /**\n * Canonical {@link @fgv/ts-utils#MessageLogLevel | log level} — drives FILTERING.\n * Set losslessly from a ts-utils logger when a message originates from the logger bridge.\n */\n readonly level: MessageLogLevel;\n /**\n * Display styling override (color / icon / toast). Optional; when absent, styling is\n * derived from {@link IMessage.level} via {@link deriveSeverityFromLevel}. `'success'`\n * lives here — set explicitly for UI-originated messages, never from a logger bridge.\n */\n readonly severity?: MessageSeverity;\n /** Message text */\n readonly text: string;\n /** Timestamp (ms since epoch) */\n readonly timestamp: number;\n /** Optional action (e.g., a link or callback) */\n readonly action?: IMessageAction;\n}\n\n/**\n * An actionable element attached to a message rendered as an external link.\n * Renderers display this as an `<a>` that opens in a new tab with\n * `noopener noreferrer`.\n * @public\n */\nexport interface IMessageHrefAction {\n /** Action label */\n readonly label: string;\n /** External URL to open when the action is clicked. */\n readonly href: string;\n readonly onAction?: undefined;\n}\n\n/**\n * An actionable element attached to a message rendered as a callback button.\n * Renderers display this as a `<button>` that invokes {@link IMessageCallbackAction.onAction}.\n * @public\n */\nexport interface IMessageCallbackAction {\n /** Action label */\n readonly label: string;\n /** Callback when the action is triggered. */\n readonly onAction: () => void;\n readonly href?: undefined;\n}\n\n/**\n * An actionable element attached to a message — either an external link\n * ({@link IMessageHrefAction}) or a callback ({@link IMessageCallbackAction}).\n * The discriminated-union shape makes \"exactly one of `href` or `onAction`\"\n * an enforced contract at type-check time, so callers cannot accidentally\n * supply both or neither.\n * @public\n */\nexport type IMessageAction = IMessageHrefAction | IMessageCallbackAction;\n\n// ============================================================================\n// Toast Configuration\n// ============================================================================\n\n/**\n * Configuration for toast auto-dismiss behavior.\n * @public\n */\nexport interface IToastConfig {\n /** Auto-dismiss duration in ms. 0 = no auto-dismiss. */\n readonly autoDismissMs: number;\n}\n\n/**\n * Default toast configuration by severity.\n * @public\n */\nexport const DEFAULT_TOAST_CONFIG: Record<MessageSeverity, IToastConfig> = {\n info: { autoDismissMs: 3000 },\n success: { autoDismissMs: 3000 },\n warning: { autoDismissMs: 5000 },\n error: { autoDismissMs: 0 }\n} as const;\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nlet _nextId: number = 0;\n\n/**\n * Generates a unique message ID.\n * @internal\n */\nexport function generateMessageId(): string {\n return `msg-${++_nextId}-${Date.now().toString(36)}`;\n}\n\n/**\n * Options for {@link createMessage} (and `addMessage`).\n * @public\n */\nexport interface ICreateMessageOptions {\n /**\n * Explicit display styling override. Omit to derive styling from the level via\n * {@link deriveSeverityFromLevel}. Set to `'success'` for UI-originated success messages.\n */\n readonly severity?: MessageSeverity;\n /** Optional action (e.g., a link or callback). */\n readonly action?: IMessageAction;\n}\n\n/**\n * Creates a new message at the given canonical {@link @fgv/ts-utils#MessageLogLevel | log level}.\n * The level drives filtering; pass `options.severity` only to override display styling\n * (e.g. `'success'`).\n * @param level - The canonical log level (filter axis).\n * @param text - The message text.\n * @param options - Optional styling severity and action.\n * @returns The created message.\n * @public\n */\nexport function createMessage(\n level: MessageLogLevel,\n text: string,\n options?: ICreateMessageOptions\n): IMessage {\n return {\n id: generateMessageId(),\n level,\n severity: options?.severity,\n text,\n timestamp: Date.now(),\n action: options?.action\n };\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
- import { IMessage, IMessageAction, MessageSeverity } from './model';
2
+ import { type MessageLogLevel } from '@fgv/ts-utils';
3
+ import { ICreateMessageOptions, IMessage } from './model';
3
4
  /**
4
5
  * Value provided by the MessagesContext.
5
6
  * @public
@@ -7,8 +8,11 @@ import { IMessage, IMessageAction, MessageSeverity } from './model';
7
8
  export interface IMessagesContextValue {
8
9
  /** All messages in the stream (newest last) */
9
10
  readonly messages: ReadonlyArray<IMessage>;
10
- /** Add a message to the stream */
11
- readonly addMessage: (severity: MessageSeverity, text: string, action?: IMessageAction) => IMessage;
11
+ /**
12
+ * Add a message to the stream. The `level` drives filtering; pass `options.severity`
13
+ * only to override display styling (e.g. `'success'`).
14
+ */
15
+ readonly addMessage: (level: MessageLogLevel, text: string, options?: ICreateMessageOptions) => IMessage;
12
16
  /** Dismiss a specific message (removes from toast display, keeps in log) */
13
17
  readonly dismissMessage: (id: string) => void;
14
18
  /** Clear all messages */
@@ -26,8 +30,48 @@ export interface IMessagesProviderProps {
26
30
  /** Children */
27
31
  readonly children: React.ReactNode;
28
32
  }
33
+ /**
34
+ * Sentinel Tailwind class used by the probe in {@link MessagesProvider}.
35
+ *
36
+ * This is an arbitrary-value height utility (`h-[7.3215px]`) chosen to be
37
+ * vanishingly unlikely to appear in any consumer's source code: a common
38
+ * utility like `h-3.5` could be generated by Tailwind scanning the consumer's
39
+ * own files, which would mask the missing `@fgv/ts-app-shell` content path.
40
+ * Tailwind JIT regex-scans the package's built JS, finds this literal string,
41
+ * and generates the rule — only when the consumer's `content` array includes
42
+ * the `@fgv/ts-app-shell/lib` glob (see the README "Setup" section).
43
+ * @internal
44
+ */
45
+ export declare const TAILWIND_PROBE_CLASS: string;
46
+ /**
47
+ * Expected probe height in pixels (matches {@link TAILWIND_PROBE_CLASS}).
48
+ * @internal
49
+ */
50
+ export declare const TAILWIND_PROBE_HEIGHT_PX: number;
51
+ /**
52
+ * URL of the setup-instructions anchor surfaced by the Tailwind-content-path
53
+ * diagnostic message when the probe detects that ts-app-shell's geometry utilities
54
+ * are not being generated.
55
+ * @internal
56
+ */
57
+ export declare const TAILWIND_SETUP_DOCS_URL: string;
58
+ /**
59
+ * Text of the diagnostic message emitted by the Tailwind-content-path probe.
60
+ * @internal
61
+ */
62
+ export declare const TAILWIND_PROBE_MESSAGE_TEXT: string;
29
63
  /**
30
64
  * Provides the global message stream to the component tree.
65
+ *
66
+ * On mount, runs a one-shot probe that detects whether the consuming app's Tailwind
67
+ * configuration is missing the `@fgv/ts-app-shell` content path. The probe applies a
68
+ * sentinel arbitrary-value class ({@link TAILWIND_PROBE_CLASS}) that is vanishingly
69
+ * unlikely to appear in any consumer's source — Tailwind will only generate the rule
70
+ * when it scans the package's built JS via the content path. If the probe finds the
71
+ * element unsized, a `'warning'` message is appended to the stream with a link to the
72
+ * setup documentation. The probe runs exactly once per provider lifecycle (idempotent
73
+ * under React.StrictMode double-render and HMR) and uses inline styles so it is not
74
+ * affected by the failure mode it detects.
31
75
  * @public
32
76
  */
33
77
  export declare function MessagesProvider(props: IMessagesProviderProps): React.ReactElement;
@@ -1 +1 @@
1
- {"version":3,"file":"MessagesContext.d.ts","sourceRoot":"","sources":["../../../src/packlets/messages/MessagesContext.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAqD,MAAM,OAAO,CAAC;AAE1E,OAAO,EAAiB,QAAQ,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAMnF;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,+CAA+C;IAC/C,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC3C,kCAAkC;IAClC,QAAQ,CAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,cAAc,KAAK,QAAQ,CAAC;IACpG,4EAA4E;IAC5E,QAAQ,CAAC,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,yBAAyB;IACzB,QAAQ,CAAC,aAAa,EAAE,MAAM,IAAI,CAAC;IACnC,+DAA+D;IAC/D,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;CAChD;AAcD;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,sDAAsD;IACtD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,eAAe;IACf,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CACpC;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,sBAAsB,GAAG,KAAK,CAAC,YAAY,CA2ClF;AAMD;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,qBAAqB,CAMnD"}
1
+ {"version":3,"file":"MessagesContext.d.ts","sourceRoot":"","sources":["../../../src/packlets/messages/MessagesContext.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAwE,MAAM,OAAO,CAAC;AAE7F,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,EAAiB,qBAAqB,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAMzE;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,+CAA+C;IAC/C,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC3C;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,KAAK,QAAQ,CAAC;IACzG,4EAA4E;IAC5E,QAAQ,CAAC,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,yBAAyB;IACzB,QAAQ,CAAC,aAAa,EAAE,MAAM,IAAI,CAAC;IACnC,+DAA+D;IAC/D,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;CAChD;AAcD;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,sDAAsD;IACtD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,eAAe;IACf,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CACpC;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAAuB,CAAC;AAE3D;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,MAAe,CAAC;AAEvD;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,MAC0C,CAAC;AAEjF;;;GAGG;AACH,eAAO,MAAM,2BAA2B,EAAE,MAEsB,CAAC;AAEjE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,sBAAsB,GAAG,KAAK,CAAC,YAAY,CAkFlF;AAMD;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,qBAAqB,CAMnD"}
@@ -54,6 +54,7 @@ var __importStar = (this && this.__importStar) || (function () {
54
54
  };
55
55
  })();
56
56
  Object.defineProperty(exports, "__esModule", { value: true });
57
+ exports.TAILWIND_PROBE_MESSAGE_TEXT = exports.TAILWIND_SETUP_DOCS_URL = exports.TAILWIND_PROBE_HEIGHT_PX = exports.TAILWIND_PROBE_CLASS = void 0;
57
58
  exports.MessagesProvider = MessagesProvider;
58
59
  exports.useMessages = useMessages;
59
60
  const react_1 = __importStar(require("react"));
@@ -62,16 +63,58 @@ const model_1 = require("./model");
62
63
  // Context
63
64
  // ============================================================================
64
65
  const MessagesContext = react_1.default.createContext(undefined);
66
+ /**
67
+ * Sentinel Tailwind class used by the probe in {@link MessagesProvider}.
68
+ *
69
+ * This is an arbitrary-value height utility (`h-[7.3215px]`) chosen to be
70
+ * vanishingly unlikely to appear in any consumer's source code: a common
71
+ * utility like `h-3.5` could be generated by Tailwind scanning the consumer's
72
+ * own files, which would mask the missing `@fgv/ts-app-shell` content path.
73
+ * Tailwind JIT regex-scans the package's built JS, finds this literal string,
74
+ * and generates the rule — only when the consumer's `content` array includes
75
+ * the `@fgv/ts-app-shell/lib` glob (see the README "Setup" section).
76
+ * @internal
77
+ */
78
+ exports.TAILWIND_PROBE_CLASS = 'h-[7.3215px]';
79
+ /**
80
+ * Expected probe height in pixels (matches {@link TAILWIND_PROBE_CLASS}).
81
+ * @internal
82
+ */
83
+ exports.TAILWIND_PROBE_HEIGHT_PX = 7.3215;
84
+ /**
85
+ * URL of the setup-instructions anchor surfaced by the Tailwind-content-path
86
+ * diagnostic message when the probe detects that ts-app-shell's geometry utilities
87
+ * are not being generated.
88
+ * @internal
89
+ */
90
+ exports.TAILWIND_SETUP_DOCS_URL = 'https://github.com/ErikFortune/fgv/tree/release/libraries/ts-app-shell#setup';
91
+ /**
92
+ * Text of the diagnostic message emitted by the Tailwind-content-path probe.
93
+ * @internal
94
+ */
95
+ exports.TAILWIND_PROBE_MESSAGE_TEXT = '@fgv/ts-app-shell styles are not fully loaded. Some icons may not render correctly. ' +
96
+ 'See setup docs for the required Tailwind content-path entry.';
65
97
  /**
66
98
  * Provides the global message stream to the component tree.
99
+ *
100
+ * On mount, runs a one-shot probe that detects whether the consuming app's Tailwind
101
+ * configuration is missing the `@fgv/ts-app-shell` content path. The probe applies a
102
+ * sentinel arbitrary-value class ({@link TAILWIND_PROBE_CLASS}) that is vanishingly
103
+ * unlikely to appear in any consumer's source — Tailwind will only generate the rule
104
+ * when it scans the package's built JS via the content path. If the probe finds the
105
+ * element unsized, a `'warning'` message is appended to the stream with a link to the
106
+ * setup documentation. The probe runs exactly once per provider lifecycle (idempotent
107
+ * under React.StrictMode double-render and HMR) and uses inline styles so it is not
108
+ * affected by the failure mode it detects.
67
109
  * @public
68
110
  */
69
111
  function MessagesProvider(props) {
70
112
  const { maxMessages = 200, children } = props;
71
113
  const [messages, setMessages] = (0, react_1.useState)([]);
72
114
  const [dismissedIds, setDismissedIds] = (0, react_1.useState)(new Set());
73
- const addMessage = (0, react_1.useCallback)((severity, text, action) => {
74
- const msg = (0, model_1.createMessage)(severity, text, action);
115
+ const probeHasRun = (0, react_1.useRef)(false);
116
+ const addMessage = (0, react_1.useCallback)((level, text, options) => {
117
+ const msg = (0, model_1.createMessage)(level, text, options);
75
118
  setMessages((prev) => {
76
119
  const next = [...prev, msg];
77
120
  return next.length > maxMessages ? next.slice(next.length - maxMessages) : next;
@@ -86,6 +129,40 @@ function MessagesProvider(props) {
86
129
  setDismissedIds(new Set());
87
130
  }, []);
88
131
  const activeToasts = (0, react_1.useMemo)(() => messages.filter((m) => !dismissedIds.has(m.id)), [messages, dismissedIds]);
132
+ (0, react_1.useEffect)(() => {
133
+ if (probeHasRun.current) {
134
+ return;
135
+ }
136
+ const probe = document.createElement('div');
137
+ probe.className = exports.TAILWIND_PROBE_CLASS;
138
+ probe.setAttribute('aria-hidden', 'true');
139
+ probe.style.position = 'absolute';
140
+ probe.style.visibility = 'hidden';
141
+ probe.style.pointerEvents = 'none';
142
+ document.body.appendChild(probe);
143
+ const frameId = requestAnimationFrame(() => {
144
+ // Mark the guard only once the measurement has actually run; otherwise a
145
+ // StrictMode cleanup-before-rAF would leave `probeHasRun = true` and the
146
+ // second setup would short-circuit, suppressing the diagnostic entirely.
147
+ probeHasRun.current = true;
148
+ const height = probe.getBoundingClientRect().height;
149
+ probe.remove();
150
+ if (height === 0) {
151
+ addMessage('warning', exports.TAILWIND_PROBE_MESSAGE_TEXT, {
152
+ action: {
153
+ label: 'Open setup docs',
154
+ href: exports.TAILWIND_SETUP_DOCS_URL
155
+ }
156
+ });
157
+ }
158
+ });
159
+ return () => {
160
+ cancelAnimationFrame(frameId);
161
+ if (probe.parentNode !== null) {
162
+ probe.remove();
163
+ }
164
+ };
165
+ }, [addMessage]);
89
166
  const value = (0, react_1.useMemo)(() => ({
90
167
  messages,
91
168
  addMessage,
@@ -1 +1 @@
1
- {"version":3,"file":"MessagesContext.js","sourceRoot":"","sources":["../../../src/packlets/messages/MessagesContext.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsDH,4CA2CC;AAWD,kCAMC;AAhHD,+CAA0E;AAE1E,mCAAmF;AAuBnF,+EAA+E;AAC/E,UAAU;AACV,+EAA+E;AAE/E,MAAM,eAAe,GAAqD,eAAK,CAAC,aAAa,CAE3F,SAAS,CAAC,CAAC;AAiBb;;;GAGG;AACH,SAAgB,gBAAgB,CAAC,KAA6B;IAC5D,MAAM,EAAE,WAAW,GAAG,GAAG,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAC9C,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,IAAA,gBAAQ,EAAa,EAAE,CAAC,CAAC;IACzD,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,IAAA,gBAAQ,EAAc,IAAI,GAAG,EAAE,CAAC,CAAC;IAEzE,MAAM,UAAU,GAAG,IAAA,mBAAW,EAC5B,CAAC,QAAyB,EAAE,IAAY,EAAE,MAAuB,EAAY,EAAE;QAC7E,MAAM,GAAG,GAAG,IAAA,qBAAa,EAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QAClD,WAAW,CAAC,CAAC,IAAI,EAAE,EAAE;YACnB,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,CAAC,CAAC;YAC5B,OAAO,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAClF,CAAC,CAAC,CAAC;QACH,OAAO,GAAG,CAAC;IACb,CAAC,EACD,CAAC,WAAW,CAAC,CACd,CAAC;IAEF,MAAM,cAAc,GAAG,IAAA,mBAAW,EAAC,CAAC,EAAU,EAAQ,EAAE;QACtD,eAAe,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACnD,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,aAAa,GAAG,IAAA,mBAAW,EAAC,GAAS,EAAE;QAC3C,WAAW,CAAC,EAAE,CAAC,CAAC;QAChB,eAAe,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;IAC7B,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,YAAY,GAAG,IAAA,eAAO,EAC1B,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EACrD,CAAC,QAAQ,EAAE,YAAY,CAAC,CACzB,CAAC;IAEF,MAAM,KAAK,GAAG,IAAA,eAAO,EACnB,GAAG,EAAE,CAAC,CAAC;QACL,QAAQ;QACR,UAAU;QACV,cAAc;QACd,aAAa;QACb,YAAY;KACb,CAAC,EACF,CAAC,QAAQ,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,CAAC,CACpE,CAAC;IAEF,OAAO,8BAAC,eAAe,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,IAAG,QAAQ,CAA4B,CAAC;AACvF,CAAC;AAED,+EAA+E;AAC/E,OAAO;AACP,+EAA+E;AAE/E;;;;GAIG;AACH,SAAgB,WAAW;IACzB,MAAM,GAAG,GAAG,IAAA,kBAAU,EAAC,eAAe,CAAC,CAAC;IACxC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport React, { useCallback, useContext, useMemo, useState } from 'react';\n\nimport { createMessage, IMessage, IMessageAction, MessageSeverity } from './model';\n\n// ============================================================================\n// Context Value\n// ============================================================================\n\n/**\n * Value provided by the MessagesContext.\n * @public\n */\nexport interface IMessagesContextValue {\n /** All messages in the stream (newest last) */\n readonly messages: ReadonlyArray<IMessage>;\n /** Add a message to the stream */\n readonly addMessage: (severity: MessageSeverity, text: string, action?: IMessageAction) => IMessage;\n /** Dismiss a specific message (removes from toast display, keeps in log) */\n readonly dismissMessage: (id: string) => void;\n /** Clear all messages */\n readonly clearMessages: () => void;\n /** Messages currently visible as toasts (not yet dismissed) */\n readonly activeToasts: ReadonlyArray<IMessage>;\n}\n\n// ============================================================================\n// Context\n// ============================================================================\n\nconst MessagesContext: React.Context<IMessagesContextValue | undefined> = React.createContext<\n IMessagesContextValue | undefined\n>(undefined);\n\n// ============================================================================\n// Provider\n// ============================================================================\n\n/**\n * Props for the MessagesProvider.\n * @public\n */\nexport interface IMessagesProviderProps {\n /** Maximum number of messages to retain in the log */\n readonly maxMessages?: number;\n /** Children */\n readonly children: React.ReactNode;\n}\n\n/**\n * Provides the global message stream to the component tree.\n * @public\n */\nexport function MessagesProvider(props: IMessagesProviderProps): React.ReactElement {\n const { maxMessages = 200, children } = props;\n const [messages, setMessages] = useState<IMessage[]>([]);\n const [dismissedIds, setDismissedIds] = useState<Set<string>>(new Set());\n\n const addMessage = useCallback(\n (severity: MessageSeverity, text: string, action?: IMessageAction): IMessage => {\n const msg = createMessage(severity, text, action);\n setMessages((prev) => {\n const next = [...prev, msg];\n return next.length > maxMessages ? next.slice(next.length - maxMessages) : next;\n });\n return msg;\n },\n [maxMessages]\n );\n\n const dismissMessage = useCallback((id: string): void => {\n setDismissedIds((prev) => new Set(prev).add(id));\n }, []);\n\n const clearMessages = useCallback((): void => {\n setMessages([]);\n setDismissedIds(new Set());\n }, []);\n\n const activeToasts = useMemo(\n () => messages.filter((m) => !dismissedIds.has(m.id)),\n [messages, dismissedIds]\n );\n\n const value = useMemo<IMessagesContextValue>(\n () => ({\n messages,\n addMessage,\n dismissMessage,\n clearMessages,\n activeToasts\n }),\n [messages, addMessage, dismissMessage, clearMessages, activeToasts]\n );\n\n return <MessagesContext.Provider value={value}>{children}</MessagesContext.Provider>;\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\n/**\n * Hook to access the messages context.\n * Must be used within a MessagesProvider.\n * @public\n */\nexport function useMessages(): IMessagesContextValue {\n const ctx = useContext(MessagesContext);\n if (ctx === undefined) {\n throw new Error('useMessages must be used within a MessagesProvider');\n }\n return ctx;\n}\n"]}
1
+ {"version":3,"file":"MessagesContext.js","sourceRoot":"","sources":["../../../src/packlets/messages/MessagesContext.tsx"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';\n\nimport { type MessageLogLevel } from '@fgv/ts-utils';\n\nimport { createMessage, ICreateMessageOptions, IMessage } from './model';\n\n// ============================================================================\n// Context Value\n// ============================================================================\n\n/**\n * Value provided by the MessagesContext.\n * @public\n */\nexport interface IMessagesContextValue {\n /** All messages in the stream (newest last) */\n readonly messages: ReadonlyArray<IMessage>;\n /**\n * Add a message to the stream. The `level` drives filtering; pass `options.severity`\n * only to override display styling (e.g. `'success'`).\n */\n readonly addMessage: (level: MessageLogLevel, text: string, options?: ICreateMessageOptions) => IMessage;\n /** Dismiss a specific message (removes from toast display, keeps in log) */\n readonly dismissMessage: (id: string) => void;\n /** Clear all messages */\n readonly clearMessages: () => void;\n /** Messages currently visible as toasts (not yet dismissed) */\n readonly activeToasts: ReadonlyArray<IMessage>;\n}\n\n// ============================================================================\n// Context\n// ============================================================================\n\nconst MessagesContext: React.Context<IMessagesContextValue | undefined> = React.createContext<\n IMessagesContextValue | undefined\n>(undefined);\n\n// ============================================================================\n// Provider\n// ============================================================================\n\n/**\n * Props for the MessagesProvider.\n * @public\n */\nexport interface IMessagesProviderProps {\n /** Maximum number of messages to retain in the log */\n readonly maxMessages?: number;\n /** Children */\n readonly children: React.ReactNode;\n}\n\n/**\n * Sentinel Tailwind class used by the probe in {@link MessagesProvider}.\n *\n * This is an arbitrary-value height utility (`h-[7.3215px]`) chosen to be\n * vanishingly unlikely to appear in any consumer's source code: a common\n * utility like `h-3.5` could be generated by Tailwind scanning the consumer's\n * own files, which would mask the missing `@fgv/ts-app-shell` content path.\n * Tailwind JIT regex-scans the package's built JS, finds this literal string,\n * and generates the rule — only when the consumer's `content` array includes\n * the `@fgv/ts-app-shell/lib` glob (see the README \"Setup\" section).\n * @internal\n */\nexport const TAILWIND_PROBE_CLASS: string = 'h-[7.3215px]';\n\n/**\n * Expected probe height in pixels (matches {@link TAILWIND_PROBE_CLASS}).\n * @internal\n */\nexport const TAILWIND_PROBE_HEIGHT_PX: number = 7.3215;\n\n/**\n * URL of the setup-instructions anchor surfaced by the Tailwind-content-path\n * diagnostic message when the probe detects that ts-app-shell's geometry utilities\n * are not being generated.\n * @internal\n */\nexport const TAILWIND_SETUP_DOCS_URL: string =\n 'https://github.com/ErikFortune/fgv/tree/release/libraries/ts-app-shell#setup';\n\n/**\n * Text of the diagnostic message emitted by the Tailwind-content-path probe.\n * @internal\n */\nexport const TAILWIND_PROBE_MESSAGE_TEXT: string =\n '@fgv/ts-app-shell styles are not fully loaded. Some icons may not render correctly. ' +\n 'See setup docs for the required Tailwind content-path entry.';\n\n/**\n * Provides the global message stream to the component tree.\n *\n * On mount, runs a one-shot probe that detects whether the consuming app's Tailwind\n * configuration is missing the `@fgv/ts-app-shell` content path. The probe applies a\n * sentinel arbitrary-value class ({@link TAILWIND_PROBE_CLASS}) that is vanishingly\n * unlikely to appear in any consumer's source — Tailwind will only generate the rule\n * when it scans the package's built JS via the content path. If the probe finds the\n * element unsized, a `'warning'` message is appended to the stream with a link to the\n * setup documentation. The probe runs exactly once per provider lifecycle (idempotent\n * under React.StrictMode double-render and HMR) and uses inline styles so it is not\n * affected by the failure mode it detects.\n * @public\n */\nexport function MessagesProvider(props: IMessagesProviderProps): React.ReactElement {\n const { maxMessages = 200, children } = props;\n const [messages, setMessages] = useState<IMessage[]>([]);\n const [dismissedIds, setDismissedIds] = useState<Set<string>>(new Set());\n const probeHasRun: React.MutableRefObject<boolean> = useRef<boolean>(false);\n\n const addMessage = useCallback(\n (level: MessageLogLevel, text: string, options?: ICreateMessageOptions): IMessage => {\n const msg = createMessage(level, text, options);\n setMessages((prev) => {\n const next = [...prev, msg];\n return next.length > maxMessages ? next.slice(next.length - maxMessages) : next;\n });\n return msg;\n },\n [maxMessages]\n );\n\n const dismissMessage = useCallback((id: string): void => {\n setDismissedIds((prev) => new Set(prev).add(id));\n }, []);\n\n const clearMessages = useCallback((): void => {\n setMessages([]);\n setDismissedIds(new Set());\n }, []);\n\n const activeToasts = useMemo(\n () => messages.filter((m) => !dismissedIds.has(m.id)),\n [messages, dismissedIds]\n );\n\n useEffect(() => {\n if (probeHasRun.current) {\n return;\n }\n\n const probe: HTMLDivElement = document.createElement('div');\n probe.className = TAILWIND_PROBE_CLASS;\n probe.setAttribute('aria-hidden', 'true');\n probe.style.position = 'absolute';\n probe.style.visibility = 'hidden';\n probe.style.pointerEvents = 'none';\n document.body.appendChild(probe);\n\n const frameId: number = requestAnimationFrame(() => {\n // Mark the guard only once the measurement has actually run; otherwise a\n // StrictMode cleanup-before-rAF would leave `probeHasRun = true` and the\n // second setup would short-circuit, suppressing the diagnostic entirely.\n probeHasRun.current = true;\n const height: number = probe.getBoundingClientRect().height;\n probe.remove();\n if (height === 0) {\n addMessage('warning', TAILWIND_PROBE_MESSAGE_TEXT, {\n action: {\n label: 'Open setup docs',\n href: TAILWIND_SETUP_DOCS_URL\n }\n });\n }\n });\n\n return (): void => {\n cancelAnimationFrame(frameId);\n if (probe.parentNode !== null) {\n probe.remove();\n }\n };\n }, [addMessage]);\n\n const value = useMemo<IMessagesContextValue>(\n () => ({\n messages,\n addMessage,\n dismissMessage,\n clearMessages,\n activeToasts\n }),\n [messages, addMessage, dismissMessage, clearMessages, activeToasts]\n );\n\n return <MessagesContext.Provider value={value}>{children}</MessagesContext.Provider>;\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\n/**\n * Hook to access the messages context.\n * Must be used within a MessagesProvider.\n * @public\n */\nexport function useMessages(): IMessagesContextValue {\n const ctx = useContext(MessagesContext);\n if (ctx === undefined) {\n throw new Error('useMessages must be used within a MessagesProvider');\n }\n return ctx;\n}\n"]}
@@ -39,10 +39,15 @@ export declare class MessagesLogger extends Logging.LoggerBase {
39
39
  */
40
40
  constructor(addMessage: IMessagesContextValue['addMessage'], logLevel?: Logging.ReporterLogLevel, defaultAction?: IMessageAction);
41
41
  /**
42
- * Routes a formatted log message into the MessagesContext.
42
+ * Routes a formatted log message into the MessagesContext, setting the message `level`
43
+ * straight through from the log call (lossless). Display styling `severity` is left
44
+ * undefined so it is derived from the level for display.
45
+ *
46
+ * Suppression by threshold is handled upstream by {@link @fgv/ts-utils#Logging.LoggerBase | LoggerBase}
47
+ * (`shouldLog` against `logLevel`) before this method is invoked.
43
48
  * @param message - The formatted message string
44
- * @param level - The log level
45
- * @returns Success with the message if it was logged, or Success with undefined if suppressed
49
+ * @param level - The {@link @fgv/ts-utils#MessageLogLevel | log level}
50
+ * @returns Success with the message that was logged
46
51
  * @internal
47
52
  */
48
53
  protected _log(message: string, level: MessageLogLevel): Success<string | undefined>;
@@ -1 +1 @@
1
- {"version":3,"file":"MessagesLogger.d.ts","sourceRoot":"","sources":["../../../src/packlets/messages/MessagesLogger.ts"],"names":[],"mappings":"AAsBA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,KAAK,eAAe,EAAE,OAAO,EAAW,MAAM,eAAe,CAAC;AAEhF,OAAO,EAAE,KAAK,cAAc,EAAwB,MAAM,SAAS,CAAC;AACpE,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AA8B/D;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,cAAe,SAAQ,OAAO,CAAC,UAAU;IACpD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsC;IAClE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAE5D;;;;;OAKG;gBAED,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,EAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC,gBAAgB,EACnC,aAAa,CAAC,EAAE,cAAc;IAOhC;;;;;;OAMG;IACH,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;CAQrF"}
1
+ {"version":3,"file":"MessagesLogger.d.ts","sourceRoot":"","sources":["../../../src/packlets/messages/MessagesLogger.ts"],"names":[],"mappings":"AAsBA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,KAAK,eAAe,EAAE,OAAO,EAAW,MAAM,eAAe,CAAC;AAEhF,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAM/D;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,cAAe,SAAQ,OAAO,CAAC,UAAU;IACpD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsC;IAClE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAE5D;;;;;OAKG;gBAED,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,EAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC,gBAAgB,EACnC,aAAa,CAAC,EAAE,cAAc;IAOhC;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;CAIrF"}
@@ -32,28 +32,6 @@ exports.MessagesLogger = void 0;
32
32
  */
33
33
  const ts_utils_1 = require("@fgv/ts-utils");
34
34
  // ============================================================================
35
- // Level Mapping
36
- // ============================================================================
37
- /**
38
- * Maps ts-utils MessageLogLevel to our MessageSeverity.
39
- * `detail` maps to `'info'` (shown when logLevel is 'detail' or 'all').
40
- * `quiet` is always suppressed (only shown when reporter level is 'all', handled upstream).
41
- * @internal
42
- */
43
- function mapLogLevel(level) {
44
- switch (level) {
45
- case 'detail':
46
- case 'info':
47
- return 'info';
48
- case 'warning':
49
- return 'warning';
50
- case 'error':
51
- return 'error';
52
- case 'quiet':
53
- return undefined;
54
- }
55
- }
56
- // ============================================================================
57
35
  // MessagesLogger
58
36
  // ============================================================================
59
37
  /**
@@ -88,19 +66,20 @@ class MessagesLogger extends ts_utils_1.Logging.LoggerBase {
88
66
  this._defaultAction = defaultAction;
89
67
  }
90
68
  /**
91
- * Routes a formatted log message into the MessagesContext.
69
+ * Routes a formatted log message into the MessagesContext, setting the message `level`
70
+ * straight through from the log call (lossless). Display styling `severity` is left
71
+ * undefined so it is derived from the level for display.
72
+ *
73
+ * Suppression by threshold is handled upstream by {@link @fgv/ts-utils#Logging.LoggerBase | LoggerBase}
74
+ * (`shouldLog` against `logLevel`) before this method is invoked.
92
75
  * @param message - The formatted message string
93
- * @param level - The log level
94
- * @returns Success with the message if it was logged, or Success with undefined if suppressed
76
+ * @param level - The {@link @fgv/ts-utils#MessageLogLevel | log level}
77
+ * @returns Success with the message that was logged
95
78
  * @internal
96
79
  */
97
80
  _log(message, level) {
98
- const severity = mapLogLevel(level);
99
- if (severity !== undefined) {
100
- this._addMessage(severity, message, this._defaultAction);
101
- return (0, ts_utils_1.succeed)(message);
102
- }
103
- return (0, ts_utils_1.succeed)(undefined);
81
+ this._addMessage(level, message, { action: this._defaultAction });
82
+ return (0, ts_utils_1.succeed)(message);
104
83
  }
105
84
  }
106
85
  exports.MessagesLogger = MessagesLogger;
@@ -1 +1 @@
1
- {"version":3,"file":"MessagesLogger.js","sourceRoot":"","sources":["../../../src/packlets/messages/MessagesLogger.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;AAEH;;;;;;;GAOG;AAEH,4CAAgF;AAKhF,+EAA+E;AAC/E,gBAAgB;AAChB,+EAA+E;AAE/E;;;;;GAKG;AACH,SAAS,WAAW,CAAC,KAAsB;IACzC,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,QAAQ,CAAC;QACd,KAAK,MAAM;YACT,OAAO,MAAM,CAAC;QAChB,KAAK,SAAS;YACZ,OAAO,SAAS,CAAC;QACnB,KAAK,OAAO;YACV,OAAO,OAAO,CAAC;QACjB,KAAK,OAAO;YACV,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,iBAAiB;AACjB,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAa,cAAe,SAAQ,kBAAO,CAAC,UAAU;IAIpD;;;;;OAKG;IACH,YACE,UAA+C,EAC/C,QAAmC,EACnC,aAA8B;QAE9B,KAAK,CAAC,QAAQ,CAAC,CAAC;QAChB,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC;QAC9B,IAAI,CAAC,cAAc,GAAG,aAAa,CAAC;IACtC,CAAC;IAED;;;;;;OAMG;IACO,IAAI,CAAC,OAAe,EAAE,KAAsB;QACpD,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;YACzD,OAAO,IAAA,kBAAO,EAAC,OAAO,CAAC,CAAC;QAC1B,CAAC;QACD,OAAO,IAAA,kBAAO,EAAC,SAAS,CAAC,CAAC;IAC5B,CAAC;CACF;AAnCD,wCAmCC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Bridge between the MessagesContext and the ts-utils ILogger/LogReporter system.\n *\n * Provides an ILogger implementation that routes log messages into the\n * MessagesContext, enabling Result.report() integration in React components.\n *\n * @packageDocumentation\n */\n\nimport { Logging, type MessageLogLevel, Success, succeed } from '@fgv/ts-utils';\n\nimport { type IMessageAction, type MessageSeverity } from './model';\nimport { type IMessagesContextValue } from './MessagesContext';\n\n// ============================================================================\n// Level Mapping\n// ============================================================================\n\n/**\n * Maps ts-utils MessageLogLevel to our MessageSeverity.\n * `detail` maps to `'info'` (shown when logLevel is 'detail' or 'all').\n * `quiet` is always suppressed (only shown when reporter level is 'all', handled upstream).\n * @internal\n */\nfunction mapLogLevel(level: MessageLogLevel): MessageSeverity | undefined {\n switch (level) {\n case 'detail':\n case 'info':\n return 'info';\n case 'warning':\n return 'warning';\n case 'error':\n return 'error';\n case 'quiet':\n return undefined;\n }\n}\n\n// ============================================================================\n// MessagesLogger\n// ============================================================================\n\n/**\n * An {@link @fgv/ts-utils#Logging.ILogger | ILogger} implementation that routes\n * messages into a {@link IMessagesContextValue | MessagesContext}.\n *\n * Use this as the `logger` parameter when creating a `LogReporter` to get\n * full `Result.report()` integration that feeds into the app's toast/log system.\n *\n * @example\n * ```typescript\n * const { addMessage } = useMessages();\n * const logger = new MessagesLogger(addMessage);\n * const reporter = new LogReporter<unknown>({ logger });\n *\n * // Now Result.report() flows into toasts + log panel\n * someResult.report(reporter, { success: 'info', failure: 'error' });\n * ```\n *\n * @public\n */\nexport class MessagesLogger extends Logging.LoggerBase {\n private readonly _addMessage: IMessagesContextValue['addMessage'];\n private readonly _defaultAction: IMessageAction | undefined;\n\n /**\n * Creates a new MessagesLogger.\n * @param addMessage - The addMessage function from MessagesContext\n * @param logLevel - The minimum log level to display (default: 'info')\n * @param defaultAction - Optional default action to attach to all messages\n */\n public constructor(\n addMessage: IMessagesContextValue['addMessage'],\n logLevel?: Logging.ReporterLogLevel,\n defaultAction?: IMessageAction\n ) {\n super(logLevel);\n this._addMessage = addMessage;\n this._defaultAction = defaultAction;\n }\n\n /**\n * Routes a formatted log message into the MessagesContext.\n * @param message - The formatted message string\n * @param level - The log level\n * @returns Success with the message if it was logged, or Success with undefined if suppressed\n * @internal\n */\n protected _log(message: string, level: MessageLogLevel): Success<string | undefined> {\n const severity = mapLogLevel(level);\n if (severity !== undefined) {\n this._addMessage(severity, message, this._defaultAction);\n return succeed(message);\n }\n return succeed(undefined);\n }\n}\n"]}
1
+ {"version":3,"file":"MessagesLogger.js","sourceRoot":"","sources":["../../../src/packlets/messages/MessagesLogger.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;AAEH;;;;;;;GAOG;AAEH,4CAAgF;AAKhF,+EAA+E;AAC/E,iBAAiB;AACjB,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAa,cAAe,SAAQ,kBAAO,CAAC,UAAU;IAIpD;;;;;OAKG;IACH,YACE,UAA+C,EAC/C,QAAmC,EACnC,aAA8B;QAE9B,KAAK,CAAC,QAAQ,CAAC,CAAC;QAChB,IAAI,CAAC,WAAW,GAAG,UAAU,CAAC;QAC9B,IAAI,CAAC,cAAc,GAAG,aAAa,CAAC;IACtC,CAAC;IAED;;;;;;;;;;;OAWG;IACO,IAAI,CAAC,OAAe,EAAE,KAAsB;QACpD,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC;QAClE,OAAO,IAAA,kBAAO,EAAC,OAAO,CAAC,CAAC;IAC1B,CAAC;CACF;AApCD,wCAoCC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Bridge between the MessagesContext and the ts-utils ILogger/LogReporter system.\n *\n * Provides an ILogger implementation that routes log messages into the\n * MessagesContext, enabling Result.report() integration in React components.\n *\n * @packageDocumentation\n */\n\nimport { Logging, type MessageLogLevel, Success, succeed } from '@fgv/ts-utils';\n\nimport { type IMessageAction } from './model';\nimport { type IMessagesContextValue } from './MessagesContext';\n\n// ============================================================================\n// MessagesLogger\n// ============================================================================\n\n/**\n * An {@link @fgv/ts-utils#Logging.ILogger | ILogger} implementation that routes\n * messages into a {@link IMessagesContextValue | MessagesContext}.\n *\n * Use this as the `logger` parameter when creating a `LogReporter` to get\n * full `Result.report()` integration that feeds into the app's toast/log system.\n *\n * @example\n * ```typescript\n * const { addMessage } = useMessages();\n * const logger = new MessagesLogger(addMessage);\n * const reporter = new LogReporter<unknown>({ logger });\n *\n * // Now Result.report() flows into toasts + log panel\n * someResult.report(reporter, { success: 'info', failure: 'error' });\n * ```\n *\n * @public\n */\nexport class MessagesLogger extends Logging.LoggerBase {\n private readonly _addMessage: IMessagesContextValue['addMessage'];\n private readonly _defaultAction: IMessageAction | undefined;\n\n /**\n * Creates a new MessagesLogger.\n * @param addMessage - The addMessage function from MessagesContext\n * @param logLevel - The minimum log level to display (default: 'info')\n * @param defaultAction - Optional default action to attach to all messages\n */\n public constructor(\n addMessage: IMessagesContextValue['addMessage'],\n logLevel?: Logging.ReporterLogLevel,\n defaultAction?: IMessageAction\n ) {\n super(logLevel);\n this._addMessage = addMessage;\n this._defaultAction = defaultAction;\n }\n\n /**\n * Routes a formatted log message into the MessagesContext, setting the message `level`\n * straight through from the log call (lossless). Display styling `severity` is left\n * undefined so it is derived from the level for display.\n *\n * Suppression by threshold is handled upstream by {@link @fgv/ts-utils#Logging.LoggerBase | LoggerBase}\n * (`shouldLog` against `logLevel`) before this method is invoked.\n * @param message - The formatted message string\n * @param level - The {@link @fgv/ts-utils#MessageLogLevel | log level}\n * @returns Success with the message that was logged\n * @internal\n */\n protected _log(message: string, level: MessageLogLevel): Success<string | undefined> {\n this._addMessage(level, message, { action: this._defaultAction });\n return succeed(message);\n }\n}\n"]}
@@ -10,12 +10,21 @@ export interface IStatusBarProps {
10
10
  readonly onClear: () => void;
11
11
  /** Initial filter level for the log panel. Defaults to 'all'. */
12
12
  readonly initialFilterLevel?: Logging.ReporterLogLevel;
13
+ /**
14
+ * Maximum height of the expanded panel as a CSS length (e.g. `'40vh'`, `'320px'`).
15
+ * The panel is bounded to this height on both desktop and mobile; the message list
16
+ * scrolls within the bound while the header and filter controls stay fixed. Defaults to `'40vh'`.
17
+ */
18
+ readonly maxExpandedHeight?: string;
19
+ /** Whether the panel is expanded on mount. Defaults to `false` (collapsed). */
20
+ readonly defaultExpanded?: boolean;
13
21
  }
14
22
  /**
15
23
  * Collapsible status bar / log panel at the bottom of the application.
16
24
  *
17
25
  * Collapsed: shows severity counts.
18
- * Expanded: filterable, searchable, copyable log of all messages.
26
+ * Expanded: filterable, searchable, copyable log of all messages, bounded to
27
+ * {@link IStatusBarProps.maxExpandedHeight} with the list scrolling within the bound.
19
28
  * @public
20
29
  */
21
30
  export declare function StatusBar(props: IStatusBarProps): React.ReactElement;
@@ -1 +1 @@
1
- {"version":3,"file":"StatusBar.d.ts","sourceRoot":"","sources":["../../../src/packlets/messages/StatusBar.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAyC,MAAM,OAAO,CAAC;AAE9D,OAAO,EAAE,OAAO,EAAwB,MAAM,eAAe,CAAC;AAI9D,OAAO,EAAE,QAAQ,EAAmB,MAAM,SAAS,CAAC;AAsDpD;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,iEAAiE;IACjE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC;CACxD;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,eAAe,GAAG,KAAK,CAAC,YAAY,CA2MpE"}
1
+ {"version":3,"file":"StatusBar.d.ts","sourceRoot":"","sources":["../../../src/packlets/messages/StatusBar.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAyC,MAAM,OAAO,CAAC;AAE9D,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AASxC,OAAO,EAA2B,QAAQ,EAAmB,MAAM,SAAS,CAAC;AA6D7E;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAC7B,iEAAiE;IACjE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC;IACvD;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,+EAA+E;IAC/E,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;CACpC;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,eAAe,GAAG,KAAK,CAAC,YAAY,CA6PpE"}