@ossy/app 3.16.0 → 3.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -609,7 +609,28 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
609
609
  'Assign components to app slots in `*.layout.jsx` via `export const slots`.',
610
610
  )
611
611
  }
612
- components.push(withSourcePath({ id, entry: url, package: entryPackage(entryInfo, appPackageName) }, entryInfo, projectRoot))
612
+ /** @type {Record<string, unknown>} */
613
+ const componentEntry = {
614
+ id,
615
+ entry: url,
616
+ package: entryPackage(entryInfo, appPackageName),
617
+ }
618
+ const widget = rawMeta.widget
619
+ if (widget && typeof widget === 'object' && !Array.isArray(widget)) {
620
+ const title = typeof widget.title === 'string'
621
+ ? widget.title.trim()
622
+ : ''
623
+ const description = typeof widget.description === 'string'
624
+ ? widget.description.trim()
625
+ : ''
626
+ if (title) {
627
+ componentEntry.widget = {
628
+ title,
629
+ ...(description ? { description } : {}),
630
+ }
631
+ }
632
+ }
633
+ components.push(withSourcePath(componentEntry, entryInfo, projectRoot))
613
634
  continue
614
635
  }
615
636
 
@@ -780,6 +801,17 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
780
801
  }
781
802
  }
782
803
 
804
+ const widgets = components
805
+ .filter((component) => component?.widget?.title)
806
+ .map((component) => ({
807
+ componentId: component.id,
808
+ title: component.widget.title,
809
+ ...(component.widget.description
810
+ ? { description: component.widget.description }
811
+ : {}),
812
+ ...(component.package ? { package: component.package } : {}),
813
+ }))
814
+
783
815
  const manifest = {
784
816
  entries,
785
817
  components,
@@ -798,6 +830,7 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
798
830
  actionRoutes,
799
831
  taskCatalog,
800
832
  taskGraphEdges,
833
+ widgets,
801
834
  }
802
835
  fs.mkdirSync(path.dirname(manifestPath), { recursive: true })
803
836
  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ossy/app",
3
- "version": "3.16.0",
3
+ "version": "3.18.0",
4
4
  "description": "",
5
5
  "source": "./src/index.js",
6
6
  "main": "./src/index.js",
@@ -50,21 +50,21 @@
50
50
  "@babel/preset-react": "^7.26.3",
51
51
  "@babel/register": "^7.25.9",
52
52
  "@ossy/authentication": "^3.11.1",
53
- "@ossy/design-system": "^3.16.0",
53
+ "@ossy/design-system": "^3.18.0",
54
54
  "@ossy/locale": "^3.4.0",
55
- "@ossy/manifest": "^3.16.0",
56
- "@ossy/package-catalog": "^3.16.0",
55
+ "@ossy/manifest": "^3.18.0",
56
+ "@ossy/package-catalog": "^3.18.0",
57
57
  "@ossy/pages": "^3.0.9",
58
- "@ossy/platform": "^3.16.0",
59
- "@ossy/resources": "^3.16.0",
58
+ "@ossy/platform": "^3.18.0",
59
+ "@ossy/resources": "^3.18.0",
60
60
  "@ossy/router": "^3.0.9",
61
- "@ossy/router-react": "^3.16.0",
62
- "@ossy/schema": "^3.16.0",
63
- "@ossy/sdk": "^3.16.0",
61
+ "@ossy/router-react": "^3.18.0",
62
+ "@ossy/schema": "^3.18.0",
63
+ "@ossy/sdk": "^3.18.0",
64
64
  "@ossy/sdk-react": "^3.12.0",
65
- "@ossy/themes": "^3.8.0",
66
- "@ossy/users": "^3.16.0",
67
- "@ossy/workspaces": "^3.16.0",
65
+ "@ossy/themes": "^3.18.0",
66
+ "@ossy/users": "^3.18.0",
67
+ "@ossy/workspaces": "^3.18.0",
68
68
  "@rollup/plugin-alias": "^6.0.0",
69
69
  "@rollup/plugin-babel": "^7.1.0",
70
70
  "@rollup/plugin-commonjs": "^29.0.0",
@@ -98,5 +98,5 @@
98
98
  "README.md",
99
99
  "tsconfig.json"
100
100
  ],
101
- "gitHead": "1872fb1467bf656284aae35881c34960c3860807"
101
+ "gitHead": "8ceef98795a01c3b3486034db1d3b72b4350ccf1"
102
102
  }
@@ -46,6 +46,11 @@ export function defaultAppSettings() {
46
46
  /** Task catalog from `app build` (ADR 0011) — for automation topology UI. */
47
47
  taskCatalog: undefined,
48
48
  taskGraphEdges: undefined,
49
+ /**
50
+ * Dashboard pin catalog from `app build` — components that export
51
+ * `metadata.widget` (any package can register).
52
+ */
53
+ widgets: undefined,
49
54
  /** Action id → HTTP route map for sdk.invoke transport routing. */
50
55
  actionRoutes: undefined,
51
56
  /**
@@ -54,5 +59,13 @@ export function defaultAppSettings() {
54
59
  * need live cross-tab cache updates (see @ossy/sdk-react README).
55
60
  */
56
61
  enablePushInvalidation: false,
62
+ /**
63
+ * SSR-only device hint (`Sec-CH-UA-Mobile` client hint / UA sniff —
64
+ * see `@ossy/platform`'s `detectMobileDevice`). Seeds
65
+ * `useCompactShellLayout`'s `getServerSnapshot` so mobile requests
66
+ * don't flash the desktop sidenav before hydration resolves the
67
+ * real `matchMedia` value (#871).
68
+ */
69
+ isMobileDevice: false,
57
70
  }
58
71
  }
@@ -1,5 +1,6 @@
1
- import { useSyncExternalStore } from 'react'
1
+ import { useCallback, useSyncExternalStore } from 'react'
2
2
  import { COMPACT_SHELL_MEDIA_QUERY } from './shellChrome.js'
3
+ import { useApp } from './AppContext.js'
3
4
 
4
5
  export { COMPACT_SHELL_MEDIA_QUERY }
5
6
 
@@ -15,10 +16,20 @@ function getSnapshot () {
15
16
  return window.matchMedia(COMPACT_SHELL_MEDIA_QUERY).matches
16
17
  }
17
18
 
18
- function getServerSnapshot () {
19
- return false
20
- }
21
-
19
+ /**
20
+ * Resolves whether the app shell should render compact (mobile) chrome.
21
+ *
22
+ * `getServerSnapshot` is seeded from `AppContext.isMobileDevice` — a
23
+ * server-computed guess based on the `Sec-CH-UA-Mobile` client hint /
24
+ * User-Agent (see `@ossy/platform`'s `detectMobileDevice`) — instead of
25
+ * hardcoding `false`. Without this, SSR always assumed desktop, so mobile
26
+ * requests rendered the full desktop sidenav and snapped closed on
27
+ * hydration (#871). The client still re-resolves the real value via
28
+ * `matchMedia` once mounted; a wrong guess only causes one corrective
29
+ * re-render instead of a guaranteed flash on every mobile page load.
30
+ */
22
31
  export function useCompactShellLayout () {
32
+ const { isMobileDevice } = useApp()
33
+ const getServerSnapshot = useCallback(() => isMobileDevice === true, [isMobileDevice])
23
34
  return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
24
35
  }
@@ -33,10 +33,11 @@ export default function DefaultHeader ({
33
33
  {...rest}
34
34
  far={(
35
35
  <View
36
- layout="row"
36
+ layout="row-wrap"
37
37
  style={{
38
- flexShrink: 0,
38
+ minWidth: 0,
39
39
  alignItems: 'center',
40
+ justifyContent: 'flex-end',
40
41
  gap: compact ? 'var(--space-xs)' : 'var(--space-s)',
41
42
  }}
42
43
  >